odfdo

odfdo

Python library for OpenDocument format (ODF)

logo

odfdo is a Python3 library implementing the ISO/IEC 26300 OpenDocument Format standard.

Project: https://github.com/jdum/odfdo

Author: jerome.dumonteil@gmail.com

License: Apache License, Version 2.0

odfdo is a derivative work of the former lpod-python project.

Installation

Installation from Pypi (recommended):

pip install odfdo

Installation from sources (requiring setuptools):

pip install .

After installation from sources, you can check everything is working (some requirements: pytest, Pillow, ...):

pytest

The tests should run for a few seconds or minutes and issue no error.

Usage

from odfdo import Document, Paragraph

doc = Document('text')
doc.body.append(Paragraph("Hello world!"))
doc.save("hello.odt")

tl;dr

'Intended Audience :: Developers'

Documentation

There is no detailed documentation or tutorial, but:

  • the recipes folder contains more than 50 working sample scripts,
  • the doc folder contains an auto generated documentation.

When installing odfdo, a few scripts are installed:

  • odfdo-diff: show a diff between two .odt document.
  • odfdo-folder: convert standard ODF file to folder and files, and reverse.
  • odfdo-show: dump text from an ODF file to the standard output, and optionally styles and meta informations.
  • odfdo-styles: command line interface tool to manipulate styles of ODF files.
  • odfdo-replace: find a pattern (regex) in an ODF file and replace by some string.
  • odfdo-highlight: highlight the text matching a pattern (regex) in an ODF file.
  • odfdo-headers: print the headers of an ODF file.

About styles: the best way to apply style is by merging styles from a template document into your generated document (See odfdo-styles script). Styles are a complex matter in ODF, so trying to generate styles programmatically is not recommended.

Limitations

odfdo is intended to facilitate the generation of ODF documents, nevertheless a basic knowledge of the ODF format is necessary.

ODF document rendering can vary greatly from software to software. Especially the "styles" of the document allow an adaptation of the rendering for a particular software.

The best (only ?) way to apply style is by merging styles from a template document into your generated document.

Related project

I you work on .ods files (spreadsheet), you may be interested by these scripts that use this library to parse/generate .ods files: https://github.com/jdum/odsgenerator and https://github.com/jdum/odsparsator

Changes from former lpod library

lpod-python was written in 2009-2010 as a Python 2.x library, see: https://github.com/lpod/lpod-python

odfdo is an adaptation of this former project. odfdo main changes from lpod:

  • odfdo requires Python version 3.9 to 3.12. For Python 3.6 to 3.8 see previous releases.
  • API change: more pythonic.
  • include recipes.
  • use Apache 2.0 license.
  1# Copyright 2018-2024 Jérôme Dumonteil
  2# Copyright (c) 2009-2010 Ars Aperta, Itaapy, Pierlis, Talend.
  3#
  4# Licensed under the Apache License, Version 2.0 (the "License");
  5# you may not use this file except in compliance with the License.
  6# You may obtain a copy of the License at
  7#
  8#     http://www.apache.org/licenses/LICENSE-2.0
  9#
 10# Unless required by applicable law or agreed to in writing, software
 11# distributed under the License is distributed on an "AS IS" BASIS,
 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13# See the License for the specific language governing permissions and
 14# limitations under the License.
 15#
 16#
 17# Authors (odfdo project): jerome.dumonteil@gmail.com
 18# The odfdo project is a derivative work of the lpod-python project:
 19# https://github.com/lpod/lpod-python
 20# Authors: David Versmisse <david.versmisse@itaapy.com>
 21#          Hervé Cauwelier <herve@itaapy.com>
 22#          Romain Gauthier <romain@itaapy.com>
 23"""
 24.. include:: ../README.md
 25"""
 26
 27__all__ = [
 28    "AnimPar",
 29    "AnimSeq",
 30    "AnimTransFilter",
 31    "Annotation",
 32    "AnnotationEnd",
 33    "BackgroundImage",
 34    "Bookmark",
 35    "BookmarkEnd",
 36    "BookmarkStart",
 37    "Cell",
 38    "ChangeInfo",
 39    "Column",
 40    "ConnectorShape",
 41    "Container",
 42    "Content",
 43    "Content",
 44    "Document",
 45    "DrawFillImage",
 46    "DrawGroup",
 47    "DrawImage",
 48    "DrawPage",
 49    "Element",
 50    "ElementTyped",
 51    "EllipseShape",
 52    "FIRST_CHILD",
 53    "Frame",
 54    "Header",
 55    "HeaderRows",
 56    "IndexTitle",
 57    "IndexTitleTemplate",
 58    "LAST_CHILD",
 59    "LineBreak",
 60    "LineShape",
 61    "Link",
 62    "List",
 63    "ListItem",
 64    "Manifest",
 65    "Meta",
 66    "NEXT_SIBLING",
 67    "NamedRange",
 68    "Note",
 69    "PREV_SIBLING",
 70    "Paragraph",
 71    "PageBreak",
 72    "RectangleShape",
 73    "Reference",
 74    "ReferenceMark",
 75    "ReferenceMarkEnd",
 76    "ReferenceMarkStart",
 77    "Row",
 78    "RowGroup",
 79    "Section",
 80    "Spacer",
 81    "Span",
 82    "Style",
 83    "Styles",
 84    "TOC",
 85    "Tab",
 86    "TabStopStyle",
 87    "Table",
 88    "Text",
 89    "TextChange",
 90    "TextChangeEnd",
 91    "TextChangeStart",
 92    "TextChangedRegion",
 93    "TextDeletion",
 94    "TextFormatChange",
 95    "TextInsertion",
 96    "TocEntryTemplate",
 97    "TrackedChanges",
 98    "UserDefined",
 99    "UserFieldDecl",
100    "UserFieldDecls",
101    "UserFieldGet",
102    "UserFieldInput",
103    "VarChapter",
104    "VarCreationDate",
105    "VarCreationTime",
106    "VarDate",
107    "VarDecl",
108    "VarDecls",
109    "VarDescription",
110    "VarFileName",
111    "VarGet",
112    "VarInitialCreator",
113    "VarKeywords",
114    "VarPageCount",
115    "VarPageNumber",
116    "VarSet",
117    "VarSubject",
118    "VarTime",
119    "VarTitle",
120    "XmlPart",
121    "__version__",
122    "create_table_cell_style",
123    "default_boolean_style",
124    "default_currency_style",
125    "default_date_style",
126    "default_frame_position_style",
127    "default_number_style",
128    "default_percentage_style",
129    "default_time_style",
130    "default_toc_level_style",
131    "hex2rgb",
132    "make_table_cell_border_string",
133    "rgb2hex",
134]
135
136
137from .bookmark import Bookmark, BookmarkEnd, BookmarkStart
138from .cell import Cell
139from .container import Container
140from .content import Content
141from .document import Document
142from .draw_page import DrawPage
143from .element import FIRST_CHILD, LAST_CHILD, NEXT_SIBLING, PREV_SIBLING, Element, Text
144from .element_typed import ElementTyped
145from .frame import Frame, default_frame_position_style
146from .header import Header
147from .header_rows import HeaderRows
148from .image import DrawFillImage, DrawImage
149from .link import Link
150from .list import List, ListItem
151from .manifest import Manifest
152from .meta import Meta
153from .note import Annotation, AnnotationEnd, Note
154from .paragraph import LineBreak, PageBreak, Paragraph, Spacer, Span, Tab
155from .reference import Reference, ReferenceMark, ReferenceMarkEnd, ReferenceMarkStart
156from .section import Section
157from .shapes import ConnectorShape, DrawGroup, EllipseShape, LineShape, RectangleShape
158from .smil import AnimPar, AnimSeq, AnimTransFilter
159from .style import (
160    BackgroundImage,
161    Style,
162    create_table_cell_style,
163    default_boolean_style,
164    default_currency_style,
165    default_date_style,
166    default_number_style,
167    default_percentage_style,
168    default_time_style,
169    hex2rgb,
170    make_table_cell_border_string,
171    rgb2hex,
172)
173from .styles import Styles
174from .table import Column, NamedRange, Row, RowGroup, Table
175from .toc import (
176    TOC,
177    IndexTitle,
178    IndexTitleTemplate,
179    TabStopStyle,
180    TocEntryTemplate,
181    default_toc_level_style,
182)
183from .tracked_changes import (
184    ChangeInfo,
185    TextChange,
186    TextChangedRegion,
187    TextChangeEnd,
188    TextChangeStart,
189    TextDeletion,
190    TextFormatChange,
191    TextInsertion,
192    TrackedChanges,
193)
194from .variable import (
195    UserDefined,
196    UserFieldDecl,
197    UserFieldDecls,
198    UserFieldGet,
199    UserFieldInput,
200    VarChapter,
201    VarCreationDate,
202    VarCreationTime,
203    VarDate,
204    VarDecl,
205    VarDecls,
206    VarDescription,
207    VarFileName,
208    VarGet,
209    VarInitialCreator,
210    VarKeywords,
211    VarPageCount,
212    VarPageNumber,
213    VarSet,
214    VarSubject,
215    VarTime,
216    VarTitle,
217)
218from .version import __version__
219from .xmlpart import XmlPart
class AnimPar(odfdo.Element):
32class AnimPar(Element):
33    """A container for SMIL Presentation Animations.
34
35    Arguments:
36
37        presentation_node_type -- default, on-click, with-previous,
38                                  after-previous, timing-root, main-sequence
39                                  and interactive-sequence
40
41        smil_begin -- indefinite, 10s, [id].click, [id].begin
42    """
43
44    _tag = "anim:par"
45    _properties = (
46        PropDef("presentation_node_type", "presentation:node-type"),
47        PropDef("smil_begin", "smil:begin"),
48    )
49
50    def __init__(
51        self,
52        presentation_node_type: str | None = None,
53        smil_begin: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        super().__init__(**kwargs)
57        if self._do_init:
58            if presentation_node_type:
59                self.presentation_node_type = presentation_node_type
60            if smil_begin:
61                self.smil_begin = smil_begin

A container for SMIL Presentation Animations.

Arguments:

presentation_node_type -- default, on-click, with-previous,
                          after-previous, timing-root, main-sequence
                          and interactive-sequence

smil_begin -- indefinite, 10s, [id].click, [id].begin
AnimPar( presentation_node_type: str | None = None, smil_begin: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        presentation_node_type: str | None = None,
53        smil_begin: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        super().__init__(**kwargs)
57        if self._do_init:
58            if presentation_node_type:
59                self.presentation_node_type = presentation_node_type
60            if smil_begin:
61                self.smil_begin = smil_begin
presentation_node_type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
smil_begin: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnimSeq(odfdo.Element):
67class AnimSeq(Element):
68    """TA container for SMIL Presentation Animations. Animations
69    inside this block are executed after the slide has executed its initial
70    transition.
71
72    Arguments:
73
74        presentation_node_type -- default, on-click, with-previous,
75                                  after-previous, timing-root, main-sequence
76                                  and interactive-sequence
77    """
78
79    _tag = "anim:seq"
80    _properties = (PropDef("presentation_node_type", "presentation:node-type"),)
81
82    def __init__(
83        self,
84        presentation_node_type: str | None = None,
85        **kwargs: Any,
86    ) -> None:
87        super().__init__(**kwargs)
88        if self._do_init and presentation_node_type:
89            self.presentation_node_type = presentation_node_type

TA container for SMIL Presentation Animations. Animations inside this block are executed after the slide has executed its initial transition.

Arguments:

presentation_node_type -- default, on-click, with-previous,
                          after-previous, timing-root, main-sequence
                          and interactive-sequence
AnimSeq(presentation_node_type: str | None = None, **kwargs: Any)
82    def __init__(
83        self,
84        presentation_node_type: str | None = None,
85        **kwargs: Any,
86    ) -> None:
87        super().__init__(**kwargs)
88        if self._do_init and presentation_node_type:
89            self.presentation_node_type = presentation_node_type
presentation_node_type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnimTransFilter(odfdo.Element):
 95class AnimTransFilter(Element):
 96    """
 97    Class to make a beautiful transition between two frames.
 98
 99    Arguments:
100      smil_dur -- XXX complete me
101
102      smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/
103                    smil-transitions.html#TransitionEffects-Appendix
104                                    to get a list of all types/subtypes
105
106      smil_direction -- forward, reverse
107
108      smil_fadeColor -- forward, reverse
109
110      smil_mode -- in, out
111    """
112
113    _tag = "anim:transitionFilter"
114    _properties = (
115        PropDef("smil_dur", "smil:dur"),
116        PropDef("smil_type", "smil:type"),
117        PropDef("smil_subtype", "smil:subtype"),
118        PropDef("smil_direction", "smil:direction"),
119        PropDef("smil_fadeColor", "smil:fadeColor"),
120        PropDef("smil_mode", "smil:mode"),
121    )
122
123    def __init__(
124        self,
125        smil_dur: str | None = None,
126        smil_type: str | None = None,
127        smil_subtype: str | None = None,
128        smil_direction: str | None = None,
129        smil_fadeColor: str | None = None,
130        smil_mode: str | None = None,
131        **kwargs: Any,
132    ) -> None:
133        super().__init__(**kwargs)
134        if self._do_init:
135            if smil_dur:
136                self.smil_dur = smil_dur
137            if smil_type:
138                self.smil_type = smil_type
139            if smil_subtype:
140                self.smil_subtype = smil_subtype
141            if smil_direction:
142                self.smil_direction = smil_direction
143            if smil_fadeColor:
144                self.smil_fadeColor = smil_fadeColor
145            if smil_mode:
146                self.smil_mode = smil_mode

Class to make a beautiful transition between two frames.

Arguments: smil_dur -- XXX complete me

smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ smil-transitions.html#TransitionEffects-Appendix to get a list of all types/subtypes

smil_direction -- forward, reverse

smil_fadeColor -- forward, reverse

smil_mode -- in, out

AnimTransFilter( smil_dur: str | None = None, smil_type: str | None = None, smil_subtype: str | None = None, smil_direction: str | None = None, smil_fadeColor: str | None = None, smil_mode: str | None = None, **kwargs: Any)
123    def __init__(
124        self,
125        smil_dur: str | None = None,
126        smil_type: str | None = None,
127        smil_subtype: str | None = None,
128        smil_direction: str | None = None,
129        smil_fadeColor: str | None = None,
130        smil_mode: str | None = None,
131        **kwargs: Any,
132    ) -> None:
133        super().__init__(**kwargs)
134        if self._do_init:
135            if smil_dur:
136                self.smil_dur = smil_dur
137            if smil_type:
138                self.smil_type = smil_type
139            if smil_subtype:
140                self.smil_subtype = smil_subtype
141            if smil_direction:
142                self.smil_direction = smil_direction
143            if smil_fadeColor:
144                self.smil_fadeColor = smil_fadeColor
145            if smil_mode:
146                self.smil_mode = smil_mode
smil_dur: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
smil_type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
smil_subtype: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
smil_direction: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
smil_fadeColor: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
smil_mode: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Annotation(odfdo.Element):
147class Annotation(Element):
148    """Annotation element credited to the given creator with the
149    given text, optionally dated (current date by default).
150    If name not provided and some parent is provided, the name is
151    autogenerated.
152
153    Arguments:
154
155        text -- str or odf_element
156
157        creator -- str
158
159        date -- datetime
160
161        name -- str
162
163        parent -- Element
164    """
165
166    _tag = "office:annotation"
167    _properties = (
168        PropDef("name", "office:name"),
169        PropDef("note_id", "text:id"),
170    )
171
172    def __init__(
173        self,
174        text_or_element: Element | str | None = None,
175        creator: str | None = None,
176        date: datetime | None = None,
177        name: str | None = None,
178        parent: Element | None = None,
179        **kwargs: Any,
180    ) -> None:
181        # fixme : use offset
182        # TODO allow paragraph and text styles
183        super().__init__(**kwargs)
184
185        if self._do_init:
186            self.note_body = text_or_element  # type:ignore
187            if creator:
188                self.dc_creator = creator
189            if date is None:
190                date = datetime.now()
191            self.dc_date = date
192            if not name:
193                name = get_unique_office_name(parent)
194                self.name = name
195
196    @property
197    def note_body(self) -> str:
198        return self.text_content
199
200    @note_body.setter
201    def note_body(self, text_or_element: Element | str | None) -> None:
202        if text_or_element is None:
203            self.text_content = ""
204        elif isinstance(text_or_element, str):
205            self.text_content = text_or_element
206        elif isinstance(text_or_element, Element):
207            self.clear()
208            self.append(text_or_element)
209        else:
210            raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"')
211
212    @property
213    def start(self) -> Element:
214        """Return self."""
215        return self
216
217    @property
218    def end(self) -> Element | None:
219        """Return the corresponding annotation-end tag or None."""
220        name = self.name
221        parent = self.parent
222        if parent is None:
223            raise ValueError("Can't find end tag: no parent available")
224        body = self.document_body
225        if not body:
226            body = parent
227        return body.get_annotation_end(name=name)
228
229    def get_annotated(
230        self,
231        as_text: bool = False,
232        no_header: bool = True,
233        clean: bool = True,
234    ) -> Element | list | str | None:
235        """Returns the annotated content from an annotation.
236
237        If no content exists (single position annotation or annotation-end not
238        found), returns [] (or "" if text flag is True).
239        If as_text is True: returns the text content.
240        If clean is True: suppress unwanted tags (deletions marks, ...)
241        If no_header is True: existing text:h are changed in text:p
242        By default: returns a list of odf_element, cleaned and without headers.
243
244        Arguments:
245
246            as_text -- boolean
247
248            clean -- boolean
249
250            no_header -- boolean
251
252        Return: list or Element or text or None
253        """
254        end = self.end
255        if end is None:
256            if as_text:
257                return ""
258            return None
259        body = self.document_body
260        if not body:
261            body = self.root
262        return body.get_between(
263            self, end, as_text=as_text, no_header=no_header, clean=clean
264        )
265
266    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
267        """Delete the given element from the XML tree. If no element is given,
268        "self" is deleted. The XML library may allow to continue to use an
269        element now "orphan" as long as you have a reference to it.
270
271        For Annotation : delete the annotation-end tag if exists.
272
273        Arguments:
274
275            child -- Element or None
276        """
277        if child is not None:  # act like normal delete
278            super().delete(child)
279            return
280        end = self.end
281        if end:
282            end.delete()
283        # act like normal delete
284        super().delete()
285
286    def check_validity(self) -> None:
287        if not self.note_body:
288            raise ValueError("Annotation must have a body")
289        if not self.dc_creator:
290            raise ValueError("Annotation must have a creator")
291        if not self.dc_date:
292            self.dc_date = datetime.now()

Annotation element credited to the given creator with the given text, optionally dated (current date by default). If name not provided and some parent is provided, the name is autogenerated.

Arguments:

text -- str or odf_element

creator -- str

date -- datetime

name -- str

parent -- Element
Annotation( text_or_element: Element | str | None = None, creator: str | None = None, date: datetime.datetime | None = None, name: str | None = None, parent: Element | None = None, **kwargs: Any)
172    def __init__(
173        self,
174        text_or_element: Element | str | None = None,
175        creator: str | None = None,
176        date: datetime | None = None,
177        name: str | None = None,
178        parent: Element | None = None,
179        **kwargs: Any,
180    ) -> None:
181        # fixme : use offset
182        # TODO allow paragraph and text styles
183        super().__init__(**kwargs)
184
185        if self._do_init:
186            self.note_body = text_or_element  # type:ignore
187            if creator:
188                self.dc_creator = creator
189            if date is None:
190                date = datetime.now()
191            self.dc_date = date
192            if not name:
193                name = get_unique_office_name(parent)
194                self.name = name
note_body: str
196    @property
197    def note_body(self) -> str:
198        return self.text_content
start: Element
212    @property
213    def start(self) -> Element:
214        """Return self."""
215        return self

Return self.

end: Element | None
217    @property
218    def end(self) -> Element | None:
219        """Return the corresponding annotation-end tag or None."""
220        name = self.name
221        parent = self.parent
222        if parent is None:
223            raise ValueError("Can't find end tag: no parent available")
224        body = self.document_body
225        if not body:
226            body = parent
227        return body.get_annotation_end(name=name)

Return the corresponding annotation-end tag or None.

def get_annotated( self, as_text: bool = False, no_header: bool = True, clean: bool = True) -> Element | list | str | None:
229    def get_annotated(
230        self,
231        as_text: bool = False,
232        no_header: bool = True,
233        clean: bool = True,
234    ) -> Element | list | str | None:
235        """Returns the annotated content from an annotation.
236
237        If no content exists (single position annotation or annotation-end not
238        found), returns [] (or "" if text flag is True).
239        If as_text is True: returns the text content.
240        If clean is True: suppress unwanted tags (deletions marks, ...)
241        If no_header is True: existing text:h are changed in text:p
242        By default: returns a list of odf_element, cleaned and without headers.
243
244        Arguments:
245
246            as_text -- boolean
247
248            clean -- boolean
249
250            no_header -- boolean
251
252        Return: list or Element or text or None
253        """
254        end = self.end
255        if end is None:
256            if as_text:
257                return ""
258            return None
259        body = self.document_body
260        if not body:
261            body = self.root
262        return body.get_between(
263            self, end, as_text=as_text, no_header=no_header, clean=clean
264        )

Returns the annotated content from an annotation.

If no content exists (single position annotation or annotation-end not found), returns [] (or "" if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of odf_element, cleaned and without headers.

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text or None

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
266    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
267        """Delete the given element from the XML tree. If no element is given,
268        "self" is deleted. The XML library may allow to continue to use an
269        element now "orphan" as long as you have a reference to it.
270
271        For Annotation : delete the annotation-end tag if exists.
272
273        Arguments:
274
275            child -- Element or None
276        """
277        if child is not None:  # act like normal delete
278            super().delete(child)
279            return
280        end = self.end
281        if end:
282            end.delete()
283        # act like normal delete
284        super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For Annotation : delete the annotation-end tag if exists.

Arguments:

child -- Element or None
def check_validity(self) -> None:
286    def check_validity(self) -> None:
287        if not self.note_body:
288            raise ValueError("Annotation must have a body")
289        if not self.dc_creator:
290            raise ValueError("Annotation must have a creator")
291        if not self.dc_date:
292            self.dc_date = datetime.now()
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
note_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnnotationEnd(odfdo.Element):
298class AnnotationEnd(Element):
299    """AnnotationEnd: the "office:annotation-end" element may be used to
300    define the end of a text range of document content that spans element
301    boundaries. In that case, an "office:annotation" element shall precede
302    the "office:annotation-end" element. Both elements shall have the same
303    value for their office:name attribute. The "office:annotation-end" element
304    shall be preceded by an "office:annotation" element that has the same
305    value for its office:name attribute as the "office:annotation-end"
306    element. An "office:annotation-end" element without a preceding
307    "office:annotation" element that has the same name assigned is ignored.
308    """
309
310    _tag = "office:annotation-end"
311    _properties = (PropDef("name", "office:name"),)
312
313    def __init__(
314        self,
315        annotation: Element | None = None,
316        name: str | None = None,
317        **kwargs: Any,
318    ) -> None:
319        """Initialize an AnnotationEnd element. Either annotation or name must be
320        provided to have proper reference for the annotation-end.
321
322        Arguments:
323
324            annotation -- odf_annotation element
325
326            name -- str
327        """
328        # fixme : use offset
329        # TODO allow paragraph and text styles
330        super().__init__(**kwargs)
331        if self._do_init:
332            if annotation:
333                name = annotation.name  # type: ignore
334            if not name:
335                raise ValueError("Annotation-end must have a name")
336            self.name = name
337
338    @property
339    def start(self) -> Element | None:
340        """Return the corresponding annotation starting tag or None."""
341        name = self.name
342        parent = self.parent
343        if parent is None:
344            raise ValueError("Can't find start tag: no parent available")
345        body = self.document_body
346        if not body:
347            body = parent
348        return body.get_annotation(name=name)
349
350    @property
351    def end(self) -> Element:
352        """Return self."""
353        return self

AnnotationEnd: the "office:annotation-end" element may be used to define the end of a text range of document content that spans element boundaries. In that case, an "office:annotation" element shall precede the "office:annotation-end" element. Both elements shall have the same value for their office:name attribute. The "office:annotation-end" element shall be preceded by an "office:annotation" element that has the same value for its office:name attribute as the "office:annotation-end" element. An "office:annotation-end" element without a preceding "office:annotation" element that has the same name assigned is ignored.

AnnotationEnd( annotation: Element | None = None, name: str | None = None, **kwargs: Any)
313    def __init__(
314        self,
315        annotation: Element | None = None,
316        name: str | None = None,
317        **kwargs: Any,
318    ) -> None:
319        """Initialize an AnnotationEnd element. Either annotation or name must be
320        provided to have proper reference for the annotation-end.
321
322        Arguments:
323
324            annotation -- odf_annotation element
325
326            name -- str
327        """
328        # fixme : use offset
329        # TODO allow paragraph and text styles
330        super().__init__(**kwargs)
331        if self._do_init:
332            if annotation:
333                name = annotation.name  # type: ignore
334            if not name:
335                raise ValueError("Annotation-end must have a name")
336            self.name = name

Initialize an AnnotationEnd element. Either annotation or name must be provided to have proper reference for the annotation-end.

Arguments:

annotation -- odf_annotation element

name -- str
start: Element | None
338    @property
339    def start(self) -> Element | None:
340        """Return the corresponding annotation starting tag or None."""
341        name = self.name
342        parent = self.parent
343        if parent is None:
344            raise ValueError("Can't find start tag: no parent available")
345        body = self.document_body
346        if not body:
347            body = parent
348        return body.get_annotation(name=name)

Return the corresponding annotation starting tag or None.

end: Element
350    @property
351    def end(self) -> Element:
352        """Return self."""
353        return self

Return self.

name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BackgroundImage(odfdo.Style, odfdo.DrawImage):
1054class BackgroundImage(Style, DrawImage):
1055    _tag = "style:background-image"
1056    _properties: tuple[PropDef, ...] = (
1057        PropDef("name", "style:name"),
1058        PropDef("display_name", "style:display-name"),
1059        PropDef("svg_font_family", "svg:font-family"),
1060        PropDef("font_family_generic", "style:font-family-generic"),
1061        PropDef("font_pitch", "style:font-pitch"),
1062        PropDef("position", "style:position", "background-image"),
1063        PropDef("repeat", "style:repeat", "background-image"),
1064        PropDef("opacity", "draw:opacity", "background-image"),
1065        PropDef("filter", "style:filter-name", "background-image"),
1066        PropDef("text_style", "text:style-name"),
1067    )
1068
1069    def __init__(
1070        self,
1071        name: str | None = None,
1072        display_name: str | None = None,
1073        position: str | None = None,
1074        repeat: str | None = None,
1075        opacity: str | None = None,
1076        filter: str | None = None,  # noqa: A002
1077        # Every other property
1078        **kwargs: Any,
1079    ):
1080        kwargs["family"] = "background-image"
1081        super().__init__(**kwargs)
1082        if self._do_init:
1083            kwargs.pop("tag", None)
1084            kwargs.pop("tag_or_elem", None)
1085            self.family = "background-image"
1086            if name:
1087                self.name = name
1088            if display_name:
1089                self.display_name = display_name
1090            if position:
1091                self.position = position
1092            if repeat:
1093                self.position = repeat
1094            if opacity:
1095                self.position = opacity
1096            if filter:
1097                self.position = filter
1098            # Every other properties
1099            for prop in BackgroundImage._properties:
1100                if prop.name in kwargs:
1101                    self.set_style_attribute(prop.attr, kwargs[prop.name])

Style class for all these tags:

'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...

BackgroundImage( name: str | None = None, display_name: str | None = None, position: str | None = None, repeat: str | None = None, opacity: str | None = None, filter: str | None = None, **kwargs: Any)
1069    def __init__(
1070        self,
1071        name: str | None = None,
1072        display_name: str | None = None,
1073        position: str | None = None,
1074        repeat: str | None = None,
1075        opacity: str | None = None,
1076        filter: str | None = None,  # noqa: A002
1077        # Every other property
1078        **kwargs: Any,
1079    ):
1080        kwargs["family"] = "background-image"
1081        super().__init__(**kwargs)
1082        if self._do_init:
1083            kwargs.pop("tag", None)
1084            kwargs.pop("tag_or_elem", None)
1085            self.family = "background-image"
1086            if name:
1087                self.name = name
1088            if display_name:
1089                self.display_name = display_name
1090            if position:
1091                self.position = position
1092            if repeat:
1093                self.position = repeat
1094            if opacity:
1095                self.position = opacity
1096            if filter:
1097                self.position = filter
1098            # Every other properties
1099            for prop in BackgroundImage._properties:
1100                if prop.name in kwargs:
1101                    self.set_style_attribute(prop.attr, kwargs[prop.name])

Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.

The display name is the name the user sees in an office application.

The parent_style is the name of the style this style will inherit from.

To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.

Arguments:

family -- 'paragraph', 'text', 'section', 'table', 'table-column',
          'table-row', 'table-cell', 'table-page', 'chart',
          'drawing-page', 'graphic', 'presentation',
          'control', 'ruby', 'list', 'number', 'page-layout'
          'font-face', or 'master-page'

name -- str

display_name -- str

parent_style -- str

area -- str

'text' Properties:

italic -- bool

bold -- bool

'paragraph' Properties:

master_page -- str

'master-page' Properties:

page_layout -- str

next_style -- str

'table-cell' Properties:

border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'

padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

'table-row' Properties:

height -- str, e.g. '5cm'

use_optimal_height -- bool

'table-column' Properties:

width -- str, e.g. '5cm'

break_before -- 'page', 'column' or 'auto'

break_after -- 'page', 'column' or 'auto'
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
display_name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
svg_font_family: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
font_family_generic: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
font_pitch: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
position: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
repeat: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
opacity: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
filter: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
text_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Style
family
get_properties
set_properties
del_properties
set_background
get_level_style
set_level_style
get_header_style
set_header_style
get_page_header
set_page_header
set_font
page_layout
next_style
parent_style
master_page
style_type
leader_style
leader_text
style_position
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
DrawImage
url
type
show
actuate
filter_name
class Bookmark(odfdo.Element):
31class Bookmark(Element):
32    """
33    Bookmark class for ODF "text:bookmark"
34
35    Arguments:
36
37        name -- str
38    """
39
40    _tag = "text:bookmark"
41    _properties = (PropDef("name", "text:name"),)
42
43    def __init__(self, name: str = "", **kwargs: Any) -> None:
44        super().__init__(**kwargs)
45        if self._do_init:
46            self.name = name

Bookmark class for ODF "text:bookmark"

Arguments:

name -- str
Bookmark(name: str = '', **kwargs: Any)
43    def __init__(self, name: str = "", **kwargs: Any) -> None:
44        super().__init__(**kwargs)
45        if self._do_init:
46            self.name = name
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BookmarkEnd(odfdo.Element):
73class BookmarkEnd(Element):
74    """
75    BookmarkEnd class for ODF "text:bookmark-end"
76
77    Arguments:
78
79        name -- str
80    """
81
82    _tag = "text:bookmark-end"
83    _properties = (PropDef("name", "text:name"),)
84
85    def __init__(self, name: str = "", **kwargs: Any) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.name = name

BookmarkEnd class for ODF "text:bookmark-end"

Arguments:

name -- str
BookmarkEnd(name: str = '', **kwargs: Any)
85    def __init__(self, name: str = "", **kwargs: Any) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.name = name
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BookmarkStart(odfdo.Element):
52class BookmarkStart(Element):
53    """
54    BookmarkStart class for ODF "text:bookmark-start"
55
56    Arguments:
57
58        name -- str
59    """
60
61    _tag = "text:bookmark-start"
62    _properties = (PropDef("name", "text:name"),)
63
64    def __init__(self, name: str = "", **kwargs: Any) -> None:
65        super().__init__(**kwargs)
66        if self._do_init:
67            self.name = name

BookmarkStart class for ODF "text:bookmark-start"

Arguments:

name -- str
BookmarkStart(name: str = '', **kwargs: Any)
64    def __init__(self, name: str = "", **kwargs: Any) -> None:
65        super().__init__(**kwargs)
66        if self._do_init:
67            self.name = name
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Cell(odfdo.ElementTyped):
 44class Cell(ElementTyped):
 45    """ "table:table-cell" table cell element."""
 46
 47    _tag = "table:table-cell"
 48    _caching = True
 49
 50    def __init__(
 51        self,
 52        value: Any = None,
 53        text: str | None = None,
 54        cell_type: str | None = None,
 55        currency: str | None = None,
 56        formula: str | None = None,
 57        repeated: int | None = None,
 58        style: str | None = None,
 59        **kwargs: Any,
 60    ) -> None:
 61        """Create a cell element containing the given value. The textual
 62        representation is automatically formatted but can be provided. Cell
 63        type can be deduced as well, unless the number is a percentage or
 64        currency. If cell type is "currency", the currency must be given.
 65        The cell can be repeated on the given number of columns.
 66
 67        Arguments:
 68
 69            value -- bool, int, float, Decimal, date, datetime, str,
 70                     timedelta
 71
 72            text -- str
 73
 74            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
 75                         'string' or 'time'
 76
 77            currency -- three-letter str
 78
 79            repeated -- int
 80
 81            style -- str
 82        """
 83        super().__init__(**kwargs)
 84        self.x = None
 85        self.y = None
 86        if self._do_init:
 87            self.set_value(
 88                value,
 89                text=text,
 90                cell_type=cell_type,
 91                currency=currency,
 92                formula=formula,
 93            )
 94            if repeated and repeated > 1:
 95                self.repeated = repeated
 96            if style is not None:
 97                self.style = style
 98
 99    @property
100    def clone(self) -> Cell:
101        clone = Element.clone.fget(self)  # type: ignore
102        clone.y = self.y
103        clone.x = self.x
104        if hasattr(self, "_tmap"):
105            if hasattr(self, "_rmap"):
106                clone._rmap = self._rmap[:]
107            clone._tmap = self._tmap[:]
108            clone._cmap = self._cmap[:]
109        return clone
110
111    @property
112    def value(
113        self,
114    ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None:
115        """Set / get the value of the cell. The type is read from the
116        'office:value-type' attribute of the cell. When setting the value,
117        the type of the value will determine the new value_type of the cell.
118
119        Warning: use this method for boolean, float or string only.
120        """
121        value_type = self.get_attribute_string("office:value-type")
122        if value_type == "boolean":
123            return self.get_attribute("office:boolean-value")
124        if value_type in {"float", "percentage", "currency"}:
125            value_decimal = Decimal(str(self.get_attribute_string("office:value")))
126            # Return 3 instead of 3.0 if possible
127            if int(value_decimal) == value_decimal:
128                return int(value_decimal)
129            return value_decimal
130        if value_type == "date":
131            value_str = str(self.get_attribute_string("office:date-value"))
132            if "T" in value_str:
133                return DateTime.decode(value_str)
134            return Date.decode(value_str)
135        if value_type == "time":
136            return Duration.decode(str(self.get_attribute_string("office:time-value")))
137        if value_type == "string":
138            value = self.get_attribute_string("office:string-value")
139            if value is not None:
140                return value
141            value_list = []
142            for para in self.get_elements("text:p"):
143                value_list.append(para.text_recursive)
144            return "\n".join(value_list)
145        return None
146
147    @value.setter
148    def value(self, value: str | bytes | bool | int | Float | Decimal | None) -> None:
149        self.clear()
150        if value is None:
151            return
152        if isinstance(value, (str, bytes)):
153            if isinstance(value, bytes):
154                value = bytes_to_str(value)
155            self.set_attribute("office:value-type", "string")
156            self.set_attribute("office:string-value", value)
157            self.text = value
158            return
159        if value is True or value is False:
160            self.set_attribute("office:value-type", "boolean")
161            value_bool = Boolean.encode(value)
162            self.set_attribute("office:boolean-value", value_bool)
163            self.text = value_bool
164            return
165        if isinstance(value, (int, Float, Decimal)):
166            self.set_attribute("office:value-type", "float")
167            value_str = str(value)
168            self.set_attribute("office:value", value_str)
169            self.text = value_str
170            return
171        raise TypeError(f"Unknown value type, try with set_value() : {value!r}")
172
173    @property
174    def float(self) -> Float:
175        """Set / get the value of the cell as a float (or 0.0)."""
176        for tag in ("office:value", "office:string-value", "office:boolean-value"):
177            read_attr = self.get_attribute(tag)
178            if isinstance(read_attr, str):
179                with contextlib.suppress(ValueError, TypeError):
180                    return Float(read_attr)
181        return 0.0
182
183    @float.setter
184    def float(self, value: str | Float | int | Decimal) -> None:
185        try:
186            value_float = Float(value)
187        except (ValueError, TypeError):
188            value_float = 0.0
189        value_str = str(value_float)
190        self.clear()
191        self.set_attribute("office:value", value_str)
192        self.set_attribute("office:value-type", "float")
193        self.text = value_str
194
195    @property
196    def string(self) -> str:
197        """Set / get the value of the cell as a string (or '')."""
198        value = self.get_attribute_string("office:string-value")
199        if isinstance(value, str):
200            return value
201        return ""
202
203    @string.setter
204    def string(
205        self,
206        value: str | bytes | int | Float | Decimal | bool | None,  # type: ignore
207    ) -> None:
208        self.clear()
209        if value is None:
210            value_str = ""
211        else:
212            value_str = str(value)
213        self.set_attribute("office:value-type", "string")
214        self.set_attribute("office:string-value", value_str)
215        self.text = value_str
216
217    def set_value(
218        self,
219        value: (
220            str  # type: ignore
221            | bytes
222            | Float
223            | int
224            | Decimal
225            | bool
226            | datetime
227            | date
228            | timedelta
229            | None
230        ),
231        text: str | None = None,
232        cell_type: str | None = None,
233        currency: str | None = None,
234        formula: str | None = None,
235    ) -> None:
236        """Set the cell state from the Python value type.
237
238        Text is how the cell is displayed. Cell type is guessed,
239        unless provided.
240
241        For monetary values, provide the name of the currency.
242
243        Arguments:
244
245            value -- Python type
246
247            text -- str
248
249            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
250                        'currency' or 'percentage'
251
252            currency -- str
253        """
254        self.clear()
255        text = self.set_value_and_type(
256            value=value,
257            text=text,
258            value_type=cell_type,
259            currency=currency,
260        )
261        if text is not None:
262            self.text_content = text
263        if formula is not None:
264            self.formula = formula
265
266    @property
267    def type(self) -> str | None:
268        """Get / set the type of the cell: boolean, float, date, string
269        or time.
270
271        Return: str | None
272        """
273        return self.get_attribute_string("office:value-type")
274
275    @type.setter
276    def type(self, cell_type: str) -> None:
277        self.set_attribute("office:value-type", cell_type)
278
279    @property
280    def currency(self) -> str | None:
281        """Get / set the currency used for monetary values.
282
283        Return: str | None
284        """
285        return self.get_attribute_string("office:currency")
286
287    @currency.setter
288    def currency(self, currency: str) -> None:
289        self.set_attribute("office:currency", currency)
290
291    def _set_repeated(self, repeated: int | None) -> None:
292        """Internal only. Set the numnber of times the cell is repeated, or
293        None to delete. Without changing cache.
294        """
295        if repeated is None or repeated < 2:
296            with contextlib.suppress(KeyError):
297                self.del_attribute("table:number-columns-repeated")
298            return
299        self.set_attribute("table:number-columns-repeated", str(repeated))
300
301    @property
302    def repeated(self) -> int | None:
303        """Get / set the number of times the cell is repeated.
304
305        Always None when using the table API.
306
307        Return: int or None
308        """
309        repeated = self.get_attribute("table:number-columns-repeated")
310        if repeated is None:
311            return None
312        return int(repeated)
313
314    @repeated.setter
315    def repeated(self, repeated: int | None) -> None:
316        self._set_repeated(repeated)
317        # update cache
318        child: Element = self
319        while True:
320            # look for Row, parent may be group of rows
321            upper = child.parent
322            if not upper:
323                # lonely cell
324                return
325            # parent may be group of rows, not table
326            if isinstance(upper, Element) and upper._tag == "table:table-row":
327                break
328            child = upper
329        # fixme : need to optimize this
330        if isinstance(upper, Element) and upper._tag == "table:table-row":
331            upper._compute_row_cache()
332
333    @property
334    def style(self) -> str | None:
335        """Get / set the style of the cell itself.
336
337        Return: str | None
338        """
339        return self.get_attribute_string("table:style-name")
340
341    @style.setter
342    def style(self, style: str | Element) -> None:
343        self.set_style_attribute("table:style-name", style)
344
345    @property
346    def formula(self) -> str | None:
347        """Get / set the formula of the cell, or None if undefined.
348
349        The formula is not interpreted in any way.
350
351        Return: str | None
352        """
353        return self.get_attribute_string("table:formula")
354
355    @formula.setter
356    def formula(self, formula: str | None) -> None:
357        self.set_attribute("table:formula", formula)
358
359    def is_empty(self, aggressive: bool = False) -> bool:
360        if self.value is not None or self.children:
361            return False
362        if not aggressive and self.style is not None:
363            return False
364        return True
365
366    def _is_spanned(self) -> bool:
367        if self.tag == "table:covered-table-cell":
368            return True
369        if self.get_attribute("table:number-columns-spanned") is not None:
370            return True
371        if self.get_attribute("table:number-rows-spanned") is not None:
372            return True
373        return False

"table:table-cell" table cell element.

Cell( value: Any = None, text: str | None = None, cell_type: str | None = None, currency: str | None = None, formula: str | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        value: Any = None,
53        text: str | None = None,
54        cell_type: str | None = None,
55        currency: str | None = None,
56        formula: str | None = None,
57        repeated: int | None = None,
58        style: str | None = None,
59        **kwargs: Any,
60    ) -> None:
61        """Create a cell element containing the given value. The textual
62        representation is automatically formatted but can be provided. Cell
63        type can be deduced as well, unless the number is a percentage or
64        currency. If cell type is "currency", the currency must be given.
65        The cell can be repeated on the given number of columns.
66
67        Arguments:
68
69            value -- bool, int, float, Decimal, date, datetime, str,
70                     timedelta
71
72            text -- str
73
74            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
75                         'string' or 'time'
76
77            currency -- three-letter str
78
79            repeated -- int
80
81            style -- str
82        """
83        super().__init__(**kwargs)
84        self.x = None
85        self.y = None
86        if self._do_init:
87            self.set_value(
88                value,
89                text=text,
90                cell_type=cell_type,
91                currency=currency,
92                formula=formula,
93            )
94            if repeated and repeated > 1:
95                self.repeated = repeated
96            if style is not None:
97                self.style = style

Create a cell element containing the given value. The textual representation is automatically formatted but can be provided. Cell type can be deduced as well, unless the number is a percentage or currency. If cell type is "currency", the currency must be given. The cell can be repeated on the given number of columns.

Arguments:

value -- bool, int, float, Decimal, date, datetime, str,
         timedelta

text -- str

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

repeated -- int

style -- str
x
y
clone: Cell
 99    @property
100    def clone(self) -> Cell:
101        clone = Element.clone.fget(self)  # type: ignore
102        clone.y = self.y
103        clone.x = self.x
104        if hasattr(self, "_tmap"):
105            if hasattr(self, "_rmap"):
106                clone._rmap = self._rmap[:]
107            clone._tmap = self._tmap[:]
108            clone._cmap = self._cmap[:]
109        return clone
value: str | bool | int | float | decimal.Decimal | datetime.date | datetime.datetime | datetime.timedelta | None
111    @property
112    def value(
113        self,
114    ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None:
115        """Set / get the value of the cell. The type is read from the
116        'office:value-type' attribute of the cell. When setting the value,
117        the type of the value will determine the new value_type of the cell.
118
119        Warning: use this method for boolean, float or string only.
120        """
121        value_type = self.get_attribute_string("office:value-type")
122        if value_type == "boolean":
123            return self.get_attribute("office:boolean-value")
124        if value_type in {"float", "percentage", "currency"}:
125            value_decimal = Decimal(str(self.get_attribute_string("office:value")))
126            # Return 3 instead of 3.0 if possible
127            if int(value_decimal) == value_decimal:
128                return int(value_decimal)
129            return value_decimal
130        if value_type == "date":
131            value_str = str(self.get_attribute_string("office:date-value"))
132            if "T" in value_str:
133                return DateTime.decode(value_str)
134            return Date.decode(value_str)
135        if value_type == "time":
136            return Duration.decode(str(self.get_attribute_string("office:time-value")))
137        if value_type == "string":
138            value = self.get_attribute_string("office:string-value")
139            if value is not None:
140                return value
141            value_list = []
142            for para in self.get_elements("text:p"):
143                value_list.append(para.text_recursive)
144            return "\n".join(value_list)
145        return None

Set / get the value of the cell. The type is read from the 'office:value-type' attribute of the cell. When setting the value, the type of the value will determine the new value_type of the cell.

Warning: use this method for boolean, float or string only.

float: float
173    @property
174    def float(self) -> Float:
175        """Set / get the value of the cell as a float (or 0.0)."""
176        for tag in ("office:value", "office:string-value", "office:boolean-value"):
177            read_attr = self.get_attribute(tag)
178            if isinstance(read_attr, str):
179                with contextlib.suppress(ValueError, TypeError):
180                    return Float(read_attr)
181        return 0.0

Set / get the value of the cell as a float (or 0.0).

string: str
195    @property
196    def string(self) -> str:
197        """Set / get the value of the cell as a string (or '')."""
198        value = self.get_attribute_string("office:string-value")
199        if isinstance(value, str):
200            return value
201        return ""

Set / get the value of the cell as a string (or '').

def set_value( self, value: str | bytes | float | int | decimal.Decimal | bool | datetime.datetime | datetime.date | datetime.timedelta | None, text: str | None = None, cell_type: str | None = None, currency: str | None = None, formula: str | None = None) -> None:
217    def set_value(
218        self,
219        value: (
220            str  # type: ignore
221            | bytes
222            | Float
223            | int
224            | Decimal
225            | bool
226            | datetime
227            | date
228            | timedelta
229            | None
230        ),
231        text: str | None = None,
232        cell_type: str | None = None,
233        currency: str | None = None,
234        formula: str | None = None,
235    ) -> None:
236        """Set the cell state from the Python value type.
237
238        Text is how the cell is displayed. Cell type is guessed,
239        unless provided.
240
241        For monetary values, provide the name of the currency.
242
243        Arguments:
244
245            value -- Python type
246
247            text -- str
248
249            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
250                        'currency' or 'percentage'
251
252            currency -- str
253        """
254        self.clear()
255        text = self.set_value_and_type(
256            value=value,
257            text=text,
258            value_type=cell_type,
259            currency=currency,
260        )
261        if text is not None:
262            self.text_content = text
263        if formula is not None:
264            self.formula = formula

Set the cell state from the Python value type.

Text is how the cell is displayed. Cell type is guessed, unless provided.

For monetary values, provide the name of the currency.

Arguments:

value -- Python type

text -- str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
            'currency' or 'percentage'

currency -- str
type: str | None
266    @property
267    def type(self) -> str | None:
268        """Get / set the type of the cell: boolean, float, date, string
269        or time.
270
271        Return: str | None
272        """
273        return self.get_attribute_string("office:value-type")

Get / set the type of the cell: boolean, float, date, string or time.

Return: str | None

currency: str | None
279    @property
280    def currency(self) -> str | None:
281        """Get / set the currency used for monetary values.
282
283        Return: str | None
284        """
285        return self.get_attribute_string("office:currency")

Get / set the currency used for monetary values.

Return: str | None

repeated: int | None
301    @property
302    def repeated(self) -> int | None:
303        """Get / set the number of times the cell is repeated.
304
305        Always None when using the table API.
306
307        Return: int or None
308        """
309        repeated = self.get_attribute("table:number-columns-repeated")
310        if repeated is None:
311            return None
312        return int(repeated)

Get / set the number of times the cell is repeated.

Always None when using the table API.

Return: int or None

style: str | None
333    @property
334    def style(self) -> str | None:
335        """Get / set the style of the cell itself.
336
337        Return: str | None
338        """
339        return self.get_attribute_string("table:style-name")

Get / set the style of the cell itself.

Return: str | None

formula: str | None
345    @property
346    def formula(self) -> str | None:
347        """Get / set the formula of the cell, or None if undefined.
348
349        The formula is not interpreted in any way.
350
351        Return: str | None
352        """
353        return self.get_attribute_string("table:formula")

Get / set the formula of the cell, or None if undefined.

The formula is not interpreted in any way.

Return: str | None

def is_empty(self, aggressive: bool = False) -> bool:
359    def is_empty(self, aggressive: bool = False) -> bool:
360        if self.value is not None or self.children:
361            return False
362        if not aggressive and self.style is not None:
363            return False
364        return True

Check if the element is empty : no text, no children, no tail.

Return: Boolean

Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ChangeInfo(odfdo.Element):
 37class ChangeInfo(Element):
 38    """The "office:change-info" element represents who made a change and when.
 39    It may also contain a comment (one or more Paragrah "text:p" elements)
 40    on the change.
 41
 42    The comments available in the ChangeInfo are available through:
 43      - get_paragraphs and get_paragraph methods for actual Paragraph.
 44      - get_comments for a plain text version
 45
 46      Arguments:
 47
 48         creator -- str (or None)
 49
 50         date -- datetime (or None)
 51    """
 52
 53    _tag = "office:change-info"
 54
 55    def __init__(
 56        self,
 57        creator: str | None = None,
 58        date: datetime | None = None,
 59        **kwargs: Any,
 60    ) -> None:
 61        super().__init__(**kwargs)
 62        if self._do_init:
 63            self.set_dc_creator(creator)
 64            self.set_dc_date(date)
 65
 66    def set_dc_creator(self, creator: str | None = None) -> None:
 67        """Set the creator of the change. Default for creator is 'Unknown'.
 68
 69        Arguments:
 70
 71            creator -- str (or None)
 72        """
 73        element = self.get_element("dc:creator")
 74        if element is None:
 75            element = Element.from_tag("dc:creator")
 76            self.insert(element, xmlposition=FIRST_CHILD)
 77        if not creator:
 78            creator = "Unknown"
 79        element.text = creator
 80
 81    def set_dc_date(self, date: datetime | None = None) -> None:
 82        """Set the date of the change. If date is None, use current time.
 83
 84        Arguments:
 85
 86            date -- datetime (or None)
 87        """
 88        if date is None:
 89            date = datetime.now()
 90        dcdate = DateTime.encode(date)
 91        element = self.get_element("dc:date")
 92        if element is None:
 93            element = Element.from_tag("dc:date")
 94            self.insert(element, xmlposition=LAST_CHILD)
 95        element.text = dcdate
 96
 97    def get_comments(self, joined: bool = True) -> str | list[str]:
 98        """Get text content of the comments. If joined is True (default), the
 99        text of different paragraphs is concatenated, else a list of strings,
100        one per paragraph, is returned.
101
102        Arguments:
103
104            joined -- boolean (default is True)
105
106        Return: str or list of str.
107        """
108        content = self.get_paragraphs()
109        if content is None:
110            content = []
111        text = [para.get_formatted_text(simple=True) for para in content]  # type: ignore
112        if joined:
113            return "\n".join(text)
114        return text
115
116    def set_comments(self, text: str = "", replace: bool = True) -> None:
117        """Set the text content of the comments. If replace is True (default),
118        the new text replace old comments, else it is added at the end.
119
120        Arguments:
121
122            text -- str
123
124            replace -- boolean
125        """
126        if replace:
127            for para in self.get_paragraphs():
128                self.delete(para)
129        para = Paragraph()
130        para.append_plain_text(text)
131        self.insert(para, xmlposition=LAST_CHILD)

The "office:change-info" element represents who made a change and when. It may also contain a comment (one or more Paragrah "text:p" elements) on the change.

The comments available in the ChangeInfo are available through:

  • get_paragraphs and get_paragraph methods for actual Paragraph.
  • get_comments for a plain text version

Arguments:

 creator -- str (or None)

 date -- datetime (or None)
ChangeInfo( creator: str | None = None, date: datetime.datetime | None = None, **kwargs: Any)
55    def __init__(
56        self,
57        creator: str | None = None,
58        date: datetime | None = None,
59        **kwargs: Any,
60    ) -> None:
61        super().__init__(**kwargs)
62        if self._do_init:
63            self.set_dc_creator(creator)
64            self.set_dc_date(date)
def set_dc_creator(self, creator: str | None = None) -> None:
66    def set_dc_creator(self, creator: str | None = None) -> None:
67        """Set the creator of the change. Default for creator is 'Unknown'.
68
69        Arguments:
70
71            creator -- str (or None)
72        """
73        element = self.get_element("dc:creator")
74        if element is None:
75            element = Element.from_tag("dc:creator")
76            self.insert(element, xmlposition=FIRST_CHILD)
77        if not creator:
78            creator = "Unknown"
79        element.text = creator

Set the creator of the change. Default for creator is 'Unknown'.

Arguments:

creator -- str (or None)
def set_dc_date(self, date: datetime.datetime | None = None) -> None:
81    def set_dc_date(self, date: datetime | None = None) -> None:
82        """Set the date of the change. If date is None, use current time.
83
84        Arguments:
85
86            date -- datetime (or None)
87        """
88        if date is None:
89            date = datetime.now()
90        dcdate = DateTime.encode(date)
91        element = self.get_element("dc:date")
92        if element is None:
93            element = Element.from_tag("dc:date")
94            self.insert(element, xmlposition=LAST_CHILD)
95        element.text = dcdate

Set the date of the change. If date is None, use current time.

Arguments:

date -- datetime (or None)
def get_comments(self, joined: bool = True) -> str | list[str]:
 97    def get_comments(self, joined: bool = True) -> str | list[str]:
 98        """Get text content of the comments. If joined is True (default), the
 99        text of different paragraphs is concatenated, else a list of strings,
100        one per paragraph, is returned.
101
102        Arguments:
103
104            joined -- boolean (default is True)
105
106        Return: str or list of str.
107        """
108        content = self.get_paragraphs()
109        if content is None:
110            content = []
111        text = [para.get_formatted_text(simple=True) for para in content]  # type: ignore
112        if joined:
113            return "\n".join(text)
114        return text

Get text content of the comments. If joined is True (default), the text of different paragraphs is concatenated, else a list of strings, one per paragraph, is returned.

Arguments:

joined -- boolean (default is True)

Return: str or list of str.

def set_comments(self, text: str = '', replace: bool = True) -> None:
116    def set_comments(self, text: str = "", replace: bool = True) -> None:
117        """Set the text content of the comments. If replace is True (default),
118        the new text replace old comments, else it is added at the end.
119
120        Arguments:
121
122            text -- str
123
124            replace -- boolean
125        """
126        if replace:
127            for para in self.get_paragraphs():
128                self.delete(para)
129        para = Paragraph()
130        para.append_plain_text(text)
131        self.insert(para, xmlposition=LAST_CHILD)

Set the text content of the comments. If replace is True (default), the new text replace old comments, else it is added at the end.

Arguments:

text -- str

replace -- boolean
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Column(odfdo.Element):
164class Column(Element):
165    """ODF table column "table:table-column" """
166
167    _tag = "table:table-column"
168    _caching = True
169
170    def __init__(
171        self,
172        default_cell_style: str | None = None,
173        repeated: int | None = None,
174        style: str | None = None,
175        **kwargs: Any,
176    ) -> None:
177        """Create a column group element of the optionally given style. Cell
178        style can be set for the whole column. If the properties apply to
179        several columns, give the number of repeated columns.
180
181        Columns don't contain cells, just style information.
182
183        You don't generally have to create columns by hand, use the Table API.
184
185        Arguments:
186
187            default_cell_style -- str
188
189            repeated -- int
190
191            style -- str
192        """
193        super().__init__(**kwargs)
194        self.x = None
195        if self._do_init:
196            if default_cell_style:
197                self.set_default_cell_style(default_cell_style)
198            if repeated and repeated > 1:
199                self.repeated = repeated
200            if style:
201                self.style = style
202
203    @property
204    def clone(self) -> Column:
205        clone = Element.clone.fget(self)  # type: ignore
206        clone.x = self.x
207        if hasattr(self, "_tmap"):
208            if hasattr(self, "_rmap"):
209                clone._rmap = self._rmap[:]
210            clone._tmap = self._tmap[:]
211            clone._cmap = self._cmap[:]
212        return clone
213
214    def get_default_cell_style(self) -> str | None:
215        return self.get_attribute_string("table:default-cell-style-name")
216
217    def set_default_cell_style(self, style: Element | str) -> None:
218        self.set_style_attribute("table:default-cell-style-name", style)
219
220    def _set_repeated(self, repeated: int | None) -> None:
221        """Internal only. Set the number of times the column is repeated, or
222        None to delete it. Without changing cache.
223
224        Arguments:
225
226            repeated -- int or None
227        """
228        if repeated is None or repeated < 2:
229            with contextlib.suppress(KeyError):
230                self.del_attribute("table:number-columns-repeated")
231            return
232        self.set_attribute("table:number-columns-repeated", str(repeated))
233
234    @property
235    def repeated(self) -> int | None:
236        """Get /set the number of times the column is repeated.
237
238        Always None when using the table API.
239
240        Return: int or None
241        """
242        repeated = self.get_attribute("table:number-columns-repeated")
243        if repeated is None:
244            return None
245        return int(repeated)
246
247    @repeated.setter
248    def repeated(self, repeated: int | None) -> None:
249        self._set_repeated(repeated)
250        # update cache
251        current: Element = self
252        while True:
253            # look for Table, parent may be group of rows
254            upper = current.parent
255            if not upper:
256                # lonely column
257                return
258            # parent may be group of rows, not table
259            if isinstance(upper, Table):
260                break
261            current = upper
262        # fixme : need to optimize this
263        if isinstance(upper, Table):
264            upper._compute_table_cache()
265            if hasattr(self, "_cmap"):
266                del self._cmap[:]
267                self._cmap.extend(upper._cmap)
268            else:
269                self._cmap = upper._cmap
270
271    @property
272    def style(self) -> str | None:
273        """Get /set the style of the column itself.
274
275        Return: str
276        """
277        return self.get_attribute_string("table:style-name")
278
279    @style.setter
280    def style(self, style: str | Element) -> None:
281        self.set_style_attribute("table:style-name", style)

ODF table column "table:table-column"

Column( default_cell_style: str | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
170    def __init__(
171        self,
172        default_cell_style: str | None = None,
173        repeated: int | None = None,
174        style: str | None = None,
175        **kwargs: Any,
176    ) -> None:
177        """Create a column group element of the optionally given style. Cell
178        style can be set for the whole column. If the properties apply to
179        several columns, give the number of repeated columns.
180
181        Columns don't contain cells, just style information.
182
183        You don't generally have to create columns by hand, use the Table API.
184
185        Arguments:
186
187            default_cell_style -- str
188
189            repeated -- int
190
191            style -- str
192        """
193        super().__init__(**kwargs)
194        self.x = None
195        if self._do_init:
196            if default_cell_style:
197                self.set_default_cell_style(default_cell_style)
198            if repeated and repeated > 1:
199                self.repeated = repeated
200            if style:
201                self.style = style

Create a column group element of the optionally given style. Cell style can be set for the whole column. If the properties apply to several columns, give the number of repeated columns.

Columns don't contain cells, just style information.

You don't generally have to create columns by hand, use the Table API.

Arguments:

default_cell_style -- str

repeated -- int

style -- str
x
clone: Column
203    @property
204    def clone(self) -> Column:
205        clone = Element.clone.fget(self)  # type: ignore
206        clone.x = self.x
207        if hasattr(self, "_tmap"):
208            if hasattr(self, "_rmap"):
209                clone._rmap = self._rmap[:]
210            clone._tmap = self._tmap[:]
211            clone._cmap = self._cmap[:]
212        return clone
def get_default_cell_style(self) -> str | None:
214    def get_default_cell_style(self) -> str | None:
215        return self.get_attribute_string("table:default-cell-style-name")
def set_default_cell_style(self, style: Element | str) -> None:
217    def set_default_cell_style(self, style: Element | str) -> None:
218        self.set_style_attribute("table:default-cell-style-name", style)
repeated: int | None
234    @property
235    def repeated(self) -> int | None:
236        """Get /set the number of times the column is repeated.
237
238        Always None when using the table API.
239
240        Return: int or None
241        """
242        repeated = self.get_attribute("table:number-columns-repeated")
243        if repeated is None:
244            return None
245        return int(repeated)

Get /set the number of times the column is repeated.

Always None when using the table API.

Return: int or None

style: str | None
271    @property
272    def style(self) -> str | None:
273        """Get /set the style of the column itself.
274
275        Return: str
276        """
277        return self.get_attribute_string("table:style-name")

Get /set the style of the column itself.

Return: str

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ConnectorShape(odfdo.shapes.ShapeBase):
242class ConnectorShape(ShapeBase):
243    """Create a Connector shape.
244
245    Arguments:
246
247        style -- str
248
249        text_style -- str
250
251        draw_id -- str
252
253        layer -- str
254
255        connected_shapes -- (shape, shape)
256
257        glue_points -- (point, point)
258
259        p1 -- (str, str)
260
261        p2 -- (str, str)
262    """
263
264    _tag = "draw:connector"
265    _properties: tuple[PropDef, ...] = (
266        PropDef("start_shape", "draw:start-shape"),
267        PropDef("end_shape", "draw:end-shape"),
268        PropDef("start_glue_point", "draw:start-glue-point"),
269        PropDef("end_glue_point", "draw:end-glue-point"),
270        PropDef("x1", "svg:x1"),
271        PropDef("y1", "svg:y1"),
272        PropDef("x2", "svg:x2"),
273        PropDef("y2", "svg:y2"),
274    )
275
276    def __init__(
277        self,
278        style: str | None = None,
279        text_style: str | None = None,
280        draw_id: str | None = None,
281        layer: str | None = None,
282        connected_shapes: tuple | None = None,
283        glue_points: tuple | None = None,
284        p1: tuple | None = None,
285        p2: tuple | None = None,
286        **kwargs: Any,
287    ) -> None:
288        kwargs.update(
289            {
290                "style": style,
291                "text_style": text_style,
292                "draw_id": draw_id,
293                "layer": layer,
294            }
295        )
296        super().__init__(**kwargs)
297        if self._do_init:
298            if connected_shapes:
299                self.start_shape = connected_shapes[0].draw_id
300                self.end_shape = connected_shapes[1].draw_id
301            if glue_points:
302                self.start_glue_point = glue_points[0]
303                self.end_glue_point = glue_points[1]
304            if p1:
305                self.x1 = p1[0]
306                self.y1 = p1[1]
307            if p2:
308                self.x2 = p2[0]
309                self.y2 = p2[1]

Create a Connector shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

connected_shapes -- (shape, shape)

glue_points -- (point, point)

p1 -- (str, str)

p2 -- (str, str)
ConnectorShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, connected_shapes: tuple | None = None, glue_points: tuple | None = None, p1: tuple | None = None, p2: tuple | None = None, **kwargs: Any)
276    def __init__(
277        self,
278        style: str | None = None,
279        text_style: str | None = None,
280        draw_id: str | None = None,
281        layer: str | None = None,
282        connected_shapes: tuple | None = None,
283        glue_points: tuple | None = None,
284        p1: tuple | None = None,
285        p2: tuple | None = None,
286        **kwargs: Any,
287    ) -> None:
288        kwargs.update(
289            {
290                "style": style,
291                "text_style": text_style,
292                "draw_id": draw_id,
293                "layer": layer,
294            }
295        )
296        super().__init__(**kwargs)
297        if self._do_init:
298            if connected_shapes:
299                self.start_shape = connected_shapes[0].draw_id
300                self.end_shape = connected_shapes[1].draw_id
301            if glue_points:
302                self.start_glue_point = glue_points[0]
303                self.end_glue_point = glue_points[1]
304            if p1:
305                self.x1 = p1[0]
306                self.y1 = p1[1]
307            if p2:
308                self.x2 = p2[0]
309                self.y2 = p2[1]
start_shape: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
end_shape: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
start_glue_point: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
end_glue_point: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
x1: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
y1: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
x2: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
y2: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class Container:
 55class Container:
 56    """Representation of the ODF file."""
 57
 58    def __init__(self, path: Path | str | io.BytesIO | None = None) -> None:
 59        self.__parts: dict[str, bytes | None] = {}
 60        self.__parts_ts: dict[str, int] = {}
 61        self.__path_like: Path | str | io.BytesIO | None = None
 62        self.__packaging: str = "zip"
 63        self.path: Path | None = None  # or Path
 64        if path:
 65            self.open(path)
 66
 67    def __repr__(self) -> str:
 68        return f"<{self.__class__.__name__} type={self.mimetype} path={self.path}>"
 69
 70    def open(self, path_or_file: Path | str | io.BytesIO) -> None:
 71        """Load the content of an ODF file."""
 72        self.__path_like = path_or_file
 73        if isinstance(path_or_file, (str, Path)):
 74            self.path = Path(path_or_file)
 75            if not self.path.exists():
 76                raise FileNotFoundError(str(self.path))
 77        if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile(
 78            path_or_file
 79        ):
 80            self.__packaging = "zip"
 81            return self._read_zip()
 82        if self.path:
 83            is_folder = False
 84            with contextlib.suppress(OSError):
 85                is_folder = self.path.is_dir()
 86            if is_folder:
 87                self.__packaging = "folder"
 88                return self._read_folder()
 89        raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")
 90
 91    def _read_zip(self) -> None:
 92        if isinstance(self.__path_like, io.BytesIO):
 93            self.__path_like.seek(0)
 94        with ZipFile(self.__path_like) as zf:  # type: ignore
 95            mimetype = bytes_to_str(zf.read("mimetype"))
 96            if mimetype not in ODF_MIMETYPES:
 97                raise ValueError(f"Document of unknown type {mimetype}")
 98            self.__parts["mimetype"] = str_to_bytes(mimetype)
 99        if self.path is None:
100            if isinstance(self.__path_like, io.BytesIO):
101                self.__path_like.seek(0)
102            # read the full file at once and forget file
103            with ZipFile(self.__path_like) as zf:  # type: ignore
104                for name in zf.namelist():
105                    upath = normalize_path(name)
106                    self.__parts[upath] = zf.read(name)
107            self.__path_like = None
108
109    def _read_folder(self) -> None:
110        try:
111            mimetype, timestamp = self._get_folder_part("mimetype")
112        except OSError:
113            printwarn("Corrupted or not an OpenDocument folder (missing mimetype)")
114            mimetype = b""
115            timestamp = int(time.time())
116        if bytes_to_str(mimetype) not in ODF_MIMETYPES:
117            message = f"Document of unknown type {mimetype!r}, try with ODF Text."
118            printwarn(message)
119            self.__parts["mimetype"] = str_to_bytes(ODF_EXTENSIONS["odt"])
120            self.__parts_ts["mimetype"] = timestamp
121
122    def _parse_folder(self, folder: str) -> list[str]:
123        parts = []
124        if self.path is None:
125            raise ValueError("Document path is not defined")
126        root = self.path / folder
127        for path in root.iterdir():
128            if path.name.startswith("."):  # no hidden files
129                continue
130            relative_path = path.relative_to(self.path)
131            if path.is_file():
132                parts.append(relative_path.as_posix())
133            if path.is_dir():
134                sub_parts = self._parse_folder(str(relative_path))
135                if sub_parts:
136                    parts.extend(sub_parts)
137                else:
138                    # store leaf directories
139                    parts.append(relative_path.as_posix() + "/")
140        return parts
141
142    def _get_folder_parts(self) -> list[str]:
143        """Get the list of members in the ODF folder."""
144        return self._parse_folder("")
145
146    def _get_folder_part(self, name: str) -> tuple[bytes, int]:
147        """Get bytes of a part from the ODF folder, with timestamp."""
148        if self.path is None:
149            raise ValueError("Document path is not defined")
150        path = self.path / name
151        timestamp = int(path.stat().st_mtime)
152        if path.is_dir():
153            return (b"", timestamp)
154        return (path.read_bytes(), timestamp)
155
156    def _get_folder_part_timestamp(self, name: str) -> int:
157        if self.path is None:
158            raise ValueError("Document path is not defined")
159        path = self.path / name
160        try:
161            timestamp = int(path.stat().st_mtime)
162        except OSError:
163            timestamp = -1
164        return timestamp
165
166    def _get_zip_part(self, name: str) -> bytes | None:
167        """Get bytes of a part from the Zip ODF file. No cache."""
168        if self.path is None:
169            raise ValueError("Document path is not defined")
170        try:
171            with ZipFile(self.path) as zf:
172                upath = normalize_path(name)
173                self.__parts[upath] = zf.read(name)
174                return self.__parts[upath]
175        except BadZipfile:
176            return None
177
178    def _get_all_zip_part(self) -> None:
179        """Read all parts. No cache."""
180        if self.path is None:
181            raise ValueError("Document path is not defined")
182        try:
183            with ZipFile(self.path) as zf:
184                for name in zf.namelist():
185                    upath = normalize_path(name)
186                    self.__parts[upath] = zf.read(name)
187        except BadZipfile:
188            pass
189
190    def _save_zip(self, target: str | Path | io.BytesIO) -> None:
191        """Save a Zip ODF from the available parts."""
192        parts = self.__parts
193        with ZipFile(target, "w", compression=ZIP_DEFLATED) as filezip:
194            # Parts to save, except manifest at the end
195            part_names = list(parts.keys())
196            try:
197                part_names.remove(ODF_MANIFEST)
198            except ValueError:
199                printwarn(f"Missing '{ODF_MANIFEST}'")
200            # "Pretty-save" parts in some order
201            # mimetype requires to be first and uncompressed
202            mimetype = parts.get("mimetype")
203            if mimetype is None:
204                raise ValueError("Mimetype is not defined")
205            try:
206                filezip.writestr("mimetype", mimetype, ZIP_STORED)
207                part_names.remove("mimetype")
208            except (ValueError, KeyError):
209                printwarn("Missing 'mimetype'")
210            # XML parts
211            for path in ODF_CONTENT, ODF_META, ODF_SETTINGS, ODF_STYLES:
212                if path not in parts:
213                    printwarn(f"Missing '{path}'")
214                    continue
215                part = parts[path]
216                if part is None:
217                    continue
218                filezip.writestr(path, part)
219                part_names.remove(path)
220            # Everything else
221            for path in part_names:
222                data = parts[path]
223                if data is None:
224                    # Deleted
225                    continue
226                filezip.writestr(path, data)
227            # Manifest
228            with contextlib.suppress(KeyError):
229                part = parts[ODF_MANIFEST]
230                if part is not None:
231                    filezip.writestr(ODF_MANIFEST, part)
232
233    def _save_folder(self, folder: Path | str) -> None:
234        """Save a folder ODF from the available parts."""
235
236        def dump(part_path: str, content: bytes) -> None:
237            if part_path.endswith("/"):  # folder
238                is_folder = True
239                pure_path = PurePath(folder, part_path[:-1])
240            else:
241                is_folder = False
242                pure_path = PurePath(folder, part_path)
243            path = Path(pure_path)
244            if is_folder:
245                path.mkdir(parents=True, exist_ok=True)
246            else:
247                path.parent.mkdir(parents=True, exist_ok=True)
248                path.write_bytes(content)
249                path.chmod(0o666)
250
251        for part_path, data in self.__parts.items():
252            if data is None:
253                # Deleted
254                continue
255            dump(part_path, data)
256
257    # Public API
258
259    def get_parts(self) -> list[str]:
260        """Get the list of members."""
261        if not self.path:
262            # maybe a file like zip archive
263            return list(self.__parts.keys())
264        if self.__packaging == "zip":
265            parts = []
266            with ZipFile(self.path) as zf:
267                for name in zf.namelist():
268                    upath = normalize_path(name)
269                    parts.append(upath)
270            return parts
271        elif self.__packaging == "folder":
272            return self._get_folder_parts()
273        else:
274            raise ValueError("Unable to provide parts of the document")
275
276    def get_part(self, path: str) -> str | bytes | None:
277        """Get the bytes of a part of the ODF."""
278        path = str(path)
279        if path in self.__parts:
280            part = self.__parts[path]
281            if part is None:
282                raise ValueError(f'Part "{path}" is deleted')
283            if self.__packaging == "folder":
284                cache_ts = self.__parts_ts.get(path, -1)
285                current_ts = self._get_folder_part_timestamp(path)
286                if current_ts != cache_ts:
287                    part, timestamp = self._get_folder_part(path)
288                    self.__parts[path] = part
289                    self.__parts_ts[path] = timestamp
290            return part
291        if self.__packaging == "zip":
292            return self._get_zip_part(path)
293        if self.__packaging == "folder":
294            part, timestamp = self._get_folder_part(path)
295            self.__parts[path] = part
296            self.__parts_ts[path] = timestamp
297            return part
298        return None
299
300    @property
301    def mimetype(self) -> str:
302        """Return str value of mimetype of the document."""
303        with contextlib.suppress(Exception):
304            b_mimetype = self.get_part("mimetype")
305            if isinstance(b_mimetype, bytes):
306                return bytes_to_str(b_mimetype)
307        return ""
308
309    @mimetype.setter
310    def mimetype(self, mimetype: str | bytes) -> None:
311        """Set mimetype value of the document."""
312        if isinstance(mimetype, str):
313            self.__parts["mimetype"] = str_to_bytes(mimetype)
314        elif isinstance(mimetype, bytes):
315            self.__parts["mimetype"] = mimetype
316        else:
317            raise TypeError(f'Wrong mimetype "{mimetype!r}"')
318
319    def set_part(self, path: str, data: bytes) -> None:
320        """Replace or add a new part."""
321        self.__parts[path] = data
322
323    def del_part(self, path: str) -> None:
324        """Mark a part for deletion."""
325        self.__parts[path] = None
326
327    @property
328    def clone(self) -> Container:
329        """Make a copy of this container with no path."""
330        if self.path and self.__packaging == "zip":
331            self._get_all_zip_part()
332        clone = deepcopy(self)
333        clone.path = None
334        return clone
335
336    @staticmethod
337    def _do_backup(target: str | Path) -> None:
338        path = Path(target)
339        if not path.exists():
340            return
341        back_file = Path(path.stem + ".backup" + path.suffix)
342        if back_file.is_dir():
343            try:
344                shutil.rmtree(back_file)
345            except OSError as e:
346                printwarn(str(e))
347        try:
348            shutil.move(target, back_file)
349        except OSError as e:
350            printwarn(str(e))
351
352    def _save_packaging(self, packaging: str | None) -> str:
353        if not packaging:
354            packaging = self.__packaging if self.__packaging else "zip"
355        packaging = packaging.strip().lower()
356        # if packaging not in ('zip', 'flat', 'folder'):
357        if packaging not in ("zip", "folder"):
358            raise ValueError(f'Packaging of type "{packaging}" is not supported')
359        return packaging
360
361    def _save_target(self, target: str | Path | io.BytesIO | None) -> str | io.BytesIO:
362        if target is None:
363            target = self.path
364        if isinstance(target, Path):
365            target = str(target)
366        if isinstance(target, str):
367            while target.endswith(os.sep):
368                target = target[:-1]
369            while target.endswith(".folder"):
370                target = target.split(".folder", 1)[0]
371        return target  # type: ignore
372
373    def _save_as_zip(self, target: str | Path | io.BytesIO, backup: bool) -> None:
374        if isinstance(target, (str, Path)) and backup:
375            self._do_backup(target)
376        self._save_zip(target)
377
378    def _save_as_folder(self, target: str | Path, backup: bool) -> None:
379        if not isinstance(target, (str, Path)):
380            raise TypeError(
381                f"Saving in folder format requires a folder name, not '{target!r}'"
382            )
383        if not str(target).endswith(".folder"):
384            target = str(target) + ".folder"
385        if backup:
386            self._do_backup(target)
387        else:
388            path = Path(target)
389            if path.exists():
390                try:
391                    shutil.rmtree(path)
392                except OSError as e:
393                    printwarn(str(e))
394        self._save_folder(target)
395
396    def save(
397        self,
398        target: str | Path | io.BytesIO | None,
399        packaging: str | None = None,
400        backup: bool = False,
401    ) -> None:
402        """Save the container to the given target, a path or a file-like
403        object.
404
405        Package the output document in the same format than current document,
406        unless "packaging" is different.
407
408        Arguments:
409
410            target -- str or file-like or Path
411
412            packaging -- 'zip', or for debugging purpose 'folder'
413
414            backup -- boolean
415        """
416        parts = self.__parts
417        packaging = self._save_packaging(packaging)
418        # Load parts else they will be considered deleted
419        for path in self.get_parts():
420            if path not in parts:
421                self.get_part(path)
422        target = self._save_target(target)
423        if packaging == "folder":
424            if isinstance(target, io.BytesIO):
425                raise TypeError(
426                    "Impossible to save on io.BytesIO with 'folder' packaging"
427                )
428            self._save_as_folder(target, backup)
429        else:
430            # default:
431            self._save_as_zip(target, backup)

Representation of the ODF file.

Container(path: pathlib.Path | str | _io.BytesIO | None = None)
58    def __init__(self, path: Path | str | io.BytesIO | None = None) -> None:
59        self.__parts: dict[str, bytes | None] = {}
60        self.__parts_ts: dict[str, int] = {}
61        self.__path_like: Path | str | io.BytesIO | None = None
62        self.__packaging: str = "zip"
63        self.path: Path | None = None  # or Path
64        if path:
65            self.open(path)
path: pathlib.Path | None
def open(self, path_or_file: pathlib.Path | str | _io.BytesIO) -> None:
70    def open(self, path_or_file: Path | str | io.BytesIO) -> None:
71        """Load the content of an ODF file."""
72        self.__path_like = path_or_file
73        if isinstance(path_or_file, (str, Path)):
74            self.path = Path(path_or_file)
75            if not self.path.exists():
76                raise FileNotFoundError(str(self.path))
77        if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile(
78            path_or_file
79        ):
80            self.__packaging = "zip"
81            return self._read_zip()
82        if self.path:
83            is_folder = False
84            with contextlib.suppress(OSError):
85                is_folder = self.path.is_dir()
86            if is_folder:
87                self.__packaging = "folder"
88                return self._read_folder()
89        raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")

Load the content of an ODF file.

def get_parts(self) -> list[str]:
259    def get_parts(self) -> list[str]:
260        """Get the list of members."""
261        if not self.path:
262            # maybe a file like zip archive
263            return list(self.__parts.keys())
264        if self.__packaging == "zip":
265            parts = []
266            with ZipFile(self.path) as zf:
267                for name in zf.namelist():
268                    upath = normalize_path(name)
269                    parts.append(upath)
270            return parts
271        elif self.__packaging == "folder":
272            return self._get_folder_parts()
273        else:
274            raise ValueError("Unable to provide parts of the document")

Get the list of members.

def get_part(self, path: str) -> str | bytes | None:
276    def get_part(self, path: str) -> str | bytes | None:
277        """Get the bytes of a part of the ODF."""
278        path = str(path)
279        if path in self.__parts:
280            part = self.__parts[path]
281            if part is None:
282                raise ValueError(f'Part "{path}" is deleted')
283            if self.__packaging == "folder":
284                cache_ts = self.__parts_ts.get(path, -1)
285                current_ts = self._get_folder_part_timestamp(path)
286                if current_ts != cache_ts:
287                    part, timestamp = self._get_folder_part(path)
288                    self.__parts[path] = part
289                    self.__parts_ts[path] = timestamp
290            return part
291        if self.__packaging == "zip":
292            return self._get_zip_part(path)
293        if self.__packaging == "folder":
294            part, timestamp = self._get_folder_part(path)
295            self.__parts[path] = part
296            self.__parts_ts[path] = timestamp
297            return part
298        return None

Get the bytes of a part of the ODF.

mimetype: str
300    @property
301    def mimetype(self) -> str:
302        """Return str value of mimetype of the document."""
303        with contextlib.suppress(Exception):
304            b_mimetype = self.get_part("mimetype")
305            if isinstance(b_mimetype, bytes):
306                return bytes_to_str(b_mimetype)
307        return ""

Return str value of mimetype of the document.

def set_part(self, path: str, data: bytes) -> None:
319    def set_part(self, path: str, data: bytes) -> None:
320        """Replace or add a new part."""
321        self.__parts[path] = data

Replace or add a new part.

def del_part(self, path: str) -> None:
323    def del_part(self, path: str) -> None:
324        """Mark a part for deletion."""
325        self.__parts[path] = None

Mark a part for deletion.

clone: Container
327    @property
328    def clone(self) -> Container:
329        """Make a copy of this container with no path."""
330        if self.path and self.__packaging == "zip":
331            self._get_all_zip_part()
332        clone = deepcopy(self)
333        clone.path = None
334        return clone

Make a copy of this container with no path.

def save( self, target: str | pathlib.Path | _io.BytesIO | None, packaging: str | None = None, backup: bool = False) -> None:
396    def save(
397        self,
398        target: str | Path | io.BytesIO | None,
399        packaging: str | None = None,
400        backup: bool = False,
401    ) -> None:
402        """Save the container to the given target, a path or a file-like
403        object.
404
405        Package the output document in the same format than current document,
406        unless "packaging" is different.
407
408        Arguments:
409
410            target -- str or file-like or Path
411
412            packaging -- 'zip', or for debugging purpose 'folder'
413
414            backup -- boolean
415        """
416        parts = self.__parts
417        packaging = self._save_packaging(packaging)
418        # Load parts else they will be considered deleted
419        for path in self.get_parts():
420            if path not in parts:
421                self.get_part(path)
422        target = self._save_target(target)
423        if packaging == "folder":
424            if isinstance(target, io.BytesIO):
425                raise TypeError(
426                    "Impossible to save on io.BytesIO with 'folder' packaging"
427                )
428            self._save_as_folder(target, backup)
429        else:
430            # default:
431            self._save_as_zip(target, backup)

Save the container to the given target, a path or a file-like object.

Package the output document in the same format than current document, unless "packaging" is different.

Arguments:

target -- str or file-like or Path

packaging -- 'zip', or for debugging purpose 'folder'

backup -- boolean
class Content(odfdo.XmlPart):
 34class Content(XmlPart):
 35    @property
 36    def body(self) -> Element:
 37        body = self.root.document_body
 38        if not isinstance(body, Element):
 39            raise ValueError("No body found in document")  # noqa:TRY004
 40        return body
 41
 42    # The following two seem useless but they match styles API
 43
 44    def _get_style_contexts(self, family: str | None) -> tuple:
 45        if family == "font-face":
 46            return (self.get_element("//office:font-face-decls"),)
 47        return (
 48            self.get_element("//office:font-face-decls"),
 49            self.get_element("//office:automatic-styles"),
 50        )
 51
 52    def __str__(self) -> str:
 53        return str(self.body)
 54
 55    # Public API
 56
 57    def get_styles(self, family: str | None = None) -> list[Style]:
 58        """Return the list of styles in the Content part, optionally limited
 59        to the given family.
 60
 61        Arguments:
 62
 63            family -- str or None
 64
 65        Return: list of Style
 66        """
 67        result: list[Style] = []
 68        for context in self._get_style_contexts(family):
 69            if context is None:
 70                continue
 71            result.extend(context.get_styles(family=family))
 72        return result
 73
 74    def get_style(
 75        self,
 76        family: str,
 77        name_or_element: str | Element | None = None,
 78        display_name: str | None = None,
 79    ) -> Style | None:
 80        """Return the style uniquely identified by the name/family pair. If
 81        the argument is already a style object, it will return it.
 82
 83        If the name is None, the default style is fetched.
 84
 85        If the name is not the internal name but the name you gave in the
 86        desktop application, use display_name instead.
 87
 88        Arguments:
 89
 90            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
 91                      'number'
 92
 93            name_or_element -- str or Style
 94
 95            display_name -- str
 96
 97        Return: Style or None if not found
 98        """
 99        for context in self._get_style_contexts(family):
100            if context is None:
101                continue
102            style = context.get_style(
103                family,
104                name_or_element=name_or_element,
105                display_name=display_name,
106            )
107            if style is not None:
108                return style
109        return None

Representation of an XML part.

Abstraction of the XML library behind.

body: Element
35    @property
36    def body(self) -> Element:
37        body = self.root.document_body
38        if not isinstance(body, Element):
39            raise ValueError("No body found in document")  # noqa:TRY004
40        return body
def get_styles(self, family: str | None = None) -> list[Style]:
57    def get_styles(self, family: str | None = None) -> list[Style]:
58        """Return the list of styles in the Content part, optionally limited
59        to the given family.
60
61        Arguments:
62
63            family -- str or None
64
65        Return: list of Style
66        """
67        result: list[Style] = []
68        for context in self._get_style_contexts(family):
69            if context is None:
70                continue
71            result.extend(context.get_styles(family=family))
72        return result

Return the list of styles in the Content part, optionally limited to the given family.

Arguments:

family -- str or None

Return: list of Style

def get_style( self, family: str, name_or_element: str | Element | None = None, display_name: str | None = None) -> Style | None:
 74    def get_style(
 75        self,
 76        family: str,
 77        name_or_element: str | Element | None = None,
 78        display_name: str | None = None,
 79    ) -> Style | None:
 80        """Return the style uniquely identified by the name/family pair. If
 81        the argument is already a style object, it will return it.
 82
 83        If the name is None, the default style is fetched.
 84
 85        If the name is not the internal name but the name you gave in the
 86        desktop application, use display_name instead.
 87
 88        Arguments:
 89
 90            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
 91                      'number'
 92
 93            name_or_element -- str or Style
 94
 95            display_name -- str
 96
 97        Return: Style or None if not found
 98        """
 99        for context in self._get_style_contexts(family):
100            if context is None:
101                continue
102            style = context.get_style(
103                family,
104                name_or_element=name_or_element,
105                display_name=display_name,
106            )
107            if style is not None:
108                return style
109        return None

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text', 'graphic', 'table', 'list',
          'number'

name_or_element -- str or Style

display_name -- str

Return: Style or None if not found

class Document:
 155class Document:
 156    """Abstraction of the ODF document.
 157
 158    To create a new Document, several possibilities:
 159
 160        - Document() or Document("text") -> an "empty" document of type text
 161        - Document("spreadsheet") -> an "empty" document of type spreadsheet
 162        - Document("presentation") -> an "empty" document of type presentation
 163        - Document("drawing") -> an "empty" document of type drawing
 164
 165        Meaning of “empty”: these documents are copies of the default
 166        templates documents provided with this library, which, as templates,
 167        are not really empty. It may be useful to clear the newly created
 168        document: document.body.clear(), or adjust meta informations like
 169        description or default language: document.meta.set_language('fr-FR')
 170
 171    If the argument is not a known template type, or is a Path,
 172    Document(file) will load the content of the ODF file.
 173
 174    To explicitly create a document from a custom template, use the
 175    Document.new(path) method whose argument is the path to the template file.
 176    """
 177
 178    def __init__(
 179        self,
 180        target: str | bytes | Path | Container | io.BytesIO | None = "text",
 181    ) -> None:
 182        # Cache of XML parts
 183        self.__xmlparts: dict[str, XmlPart] = {}
 184        # Cache of the body
 185        self.__body: Element | None = None
 186        self.container: Container | None = None
 187        if isinstance(target, bytes):
 188            # eager conversion
 189            target = bytes_to_str(target)
 190        if target is None:
 191            # empty document, you probably don't wnat this.
 192            self.container = Container()
 193            return
 194        if isinstance(target, Path):
 195            # let's assume we open a container on existing file
 196            self.container = Container(target)
 197            return
 198        if isinstance(target, Container):
 199            # special internal case, use an existing container
 200            self.container = target
 201            return
 202        if isinstance(target, str):
 203            if target in ODF_TEMPLATES:
 204                # assuming a new document from templates
 205                self.container = container_from_template(target)
 206                return
 207            # let's assume we open a container on existing file
 208            self.container = Container(target)
 209            return
 210        if isinstance(target, io.BytesIO):
 211            self.container = Container(target)
 212            return
 213        raise TypeError(f"Unknown Document source type: '{target!r}'")
 214
 215    def __repr__(self) -> str:
 216        return f"<{self.__class__.__name__} type={self.get_type()} path={self.path}>"
 217
 218    def __str__(self) -> str:
 219        try:
 220            return str(self.get_formatted_text())
 221        except NotImplementedError:
 222            return self.body.text_recursive
 223
 224    @classmethod
 225    def new(cls, template: str | Path | io.BytesIO = "text") -> Document:
 226        """Create a Document from a template.
 227
 228        The template argument is expected to be the path to a ODF template.
 229
 230        Arguments:
 231
 232            template -- str or Path or file-like (io.BytesIO)
 233
 234        Return : ODF document -- Document
 235        """
 236        container = container_from_template(template)
 237        return cls(container)
 238
 239    # Public API
 240
 241    @property
 242    def path(self) -> Path | None:
 243        """Shortcut to Document.Container.path."""
 244        if not self.container:
 245            return None
 246        return self.container.path
 247
 248    @path.setter
 249    def path(self, path_or_str: str | Path) -> None:
 250        """Shortcut to Document.Container.path
 251
 252        Only accepting str or Path."""
 253        if not self.container:
 254            return
 255        self.container.path = Path(path_or_str)
 256
 257    def get_parts(self) -> list[str]:
 258        """Return available part names with path inside the archive, e.g.
 259        ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
 260        """
 261        if not self.container:
 262            raise ValueError("Empty Container")
 263        return self.container.get_parts()
 264
 265    def get_part(self, path: str) -> XmlPart | str | bytes | None:
 266        """Return the bytes of the given part. The path is relative to the
 267        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
 268
 269        'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts
 270        to the real path, e.g. content.xml, and return a dedicated object with
 271        its own API.
 272
 273        path formated as URI, so always use '/' separator
 274        """
 275        if not self.container:
 276            raise ValueError("Empty Container")
 277        # "./ObjectReplacements/Object 1"
 278        path = path.lstrip("./")
 279        path = _get_part_path(path)
 280        cls = _get_part_class(path)
 281        # Raw bytes
 282        if cls is None:
 283            return self.container.get_part(path)
 284        # XML part
 285        part = self.__xmlparts.get(path)
 286        if part is None:
 287            self.__xmlparts[path] = part = cls(path, self.container)
 288        return part
 289
 290    def set_part(self, path: str, data: bytes) -> None:
 291        """Set the bytes of the given part. The path is relative to the
 292        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
 293
 294        path formated as URI, so always use '/' separator
 295        """
 296        if not self.container:
 297            raise ValueError("Empty Container")
 298        # "./ObjectReplacements/Object 1"
 299        path = path.lstrip("./")
 300        path = _get_part_path(path)
 301        cls = _get_part_class(path)
 302        # XML part overwritten
 303        if cls is not None:
 304            with suppress(KeyError):
 305                self.__xmlparts[path]
 306        self.container.set_part(path, data)
 307
 308    def del_part(self, path: str) -> None:
 309        """Mark a part for deletion. The path is relative to the archive,
 310        e.g. "Pictures/1003200258912EB1C3.jpg"
 311        """
 312        if not self.container:
 313            raise ValueError("Empty Container")
 314        path = _get_part_path(path)
 315        cls = _get_part_class(path)
 316        if path == ODF_MANIFEST or cls is not None:
 317            raise ValueError(f"part '{path}' is mandatory")
 318        self.container.del_part(path)
 319
 320    @property
 321    def mimetype(self) -> str:
 322        if not self.container:
 323            raise ValueError("Empty Container")
 324        return self.container.mimetype
 325
 326    @mimetype.setter
 327    def mimetype(self, mimetype: str) -> None:
 328        if not self.container:
 329            raise ValueError("Empty Container")
 330        self.container.mimetype = mimetype
 331
 332    def get_type(self) -> str:
 333        """Get the ODF type (also called class) of this document.
 334
 335        Return: 'chart', 'database', 'formula', 'graphics',
 336            'graphics-template', 'image', 'presentation',
 337            'presentation-template', 'spreadsheet', 'spreadsheet-template',
 338            'text', 'text-master', 'text-template' or 'text-web'
 339        """
 340        # The mimetype must be with the form:
 341        # application/vnd.oasis.opendocument.text
 342
 343        # Isolate and return the last part
 344        return self.mimetype.rsplit(".", 1)[-1]
 345
 346    @property
 347    def body(self) -> Element:
 348        """Return the body element of the content part, where actual content
 349        is stored.
 350        """
 351        if self.__body is None:
 352            self.__body = self.content.body
 353        return self.__body
 354
 355    @property
 356    def meta(self) -> Meta:
 357        """Return the meta part (meta.xml) of the document, where meta data
 358        are stored."""
 359        metadata = self.get_part(ODF_META)
 360        if metadata is None or not isinstance(metadata, Meta):
 361            raise ValueError("Empty Meta")
 362        return metadata
 363
 364    @property
 365    def manifest(self) -> Manifest:
 366        """Return the manifest part (manifest.xml) of the document."""
 367        manifest = self.get_part(ODF_MANIFEST)
 368        if manifest is None or not isinstance(manifest, Manifest):
 369            raise ValueError("Empty Manifest")
 370        return manifest
 371
 372    def _get_formatted_text_footnotes(
 373        self,
 374        result: list[str],
 375        context: dict,
 376        rst_mode: bool,
 377    ) -> None:
 378        # Separate text from notes
 379        if rst_mode:
 380            result.append("\n")
 381        else:
 382            result.append("----\n")
 383        for citation, body in context["footnotes"]:
 384            if rst_mode:
 385                result.append(f".. [#] {body}\n")
 386            else:
 387                result.append(f"[{citation}] {body}\n")
 388        # Append a \n after the notes
 389        result.append("\n")
 390        # Reset for the next paragraph
 391        context["footnotes"] = []
 392
 393    def _get_formatted_text_annotations(
 394        self,
 395        result: list[str],
 396        context: dict,
 397        rst_mode: bool,
 398    ) -> None:
 399        # Insert the annotations
 400        # With a separation
 401        if rst_mode:
 402            result.append("\n")
 403        else:
 404            result.append("----\n")
 405        for annotation in context["annotations"]:
 406            if rst_mode:
 407                result.append(f".. [#] {annotation}\n")
 408            else:
 409                result.append(f"[*] {annotation}\n")
 410        context["annotations"] = []
 411
 412    def _get_formatted_text_images(
 413        self,
 414        result: list[str],
 415        context: dict,
 416        rst_mode: bool,
 417    ) -> None:
 418        # Insert the images ref, only in rst mode
 419        result.append("\n")
 420        for ref, filename, (width, height) in context["images"]:
 421            result.append(f".. {ref} image:: {filename}\n")
 422            if width is not None:
 423                result.append(f"   :width: {width}\n")
 424            if height is not None:
 425                result.append(f"   :height: {height}\n")
 426        context["images"] = []
 427
 428    def _get_formatted_text_endnotes(
 429        self,
 430        result: list[str],
 431        context: dict,
 432        rst_mode: bool,
 433    ) -> None:
 434        # Append the end notes
 435        if rst_mode:
 436            result.append("\n\n")
 437        else:
 438            result.append("\n========\n")
 439        for citation, body in context["endnotes"]:
 440            if rst_mode:
 441                result.append(f".. [*] {body}\n")
 442            else:
 443                result.append(f"({citation}) {body}\n")
 444
 445    def get_formatted_text(self, rst_mode: bool = False) -> str:
 446        """Return content as text, with some formatting."""
 447        # For the moment, only "type='text'"
 448        doc_type = self.get_type()
 449        if doc_type == "spreadsheet":
 450            return self._tables_csv()
 451        if doc_type in {
 452            "text",
 453            "text-template",
 454            "presentation",
 455            "presentation-template",
 456        }:
 457            return self._formatted_text(rst_mode)
 458        raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")
 459
 460    def _tables_csv(self) -> str:
 461        return "\n\n".join(str(table) for table in self.body.get_tables())
 462
 463    def _formatted_text(self, rst_mode: bool) -> str:
 464        # Initialize an empty context
 465        context = {
 466            "document": self,
 467            "footnotes": [],
 468            "endnotes": [],
 469            "annotations": [],
 470            "rst_mode": rst_mode,
 471            "img_counter": 0,
 472            "images": [],
 473            "no_img_level": 0,
 474        }
 475        body = self.body
 476        # Get the text
 477        result = []
 478        for child in body.children:
 479            # self._get_formatted_text_child(result, element, context, rst_mode)
 480            # if child.tag == "table:table":
 481            #     result.append(child.get_formatted_text(context))
 482            #     return
 483            result.append(child.get_formatted_text(context))
 484            if context["footnotes"]:
 485                self._get_formatted_text_footnotes(result, context, rst_mode)
 486            if context["annotations"]:
 487                self._get_formatted_text_annotations(result, context, rst_mode)
 488            # Insert the images ref, only in rst mode
 489            if context["images"]:
 490                self._get_formatted_text_images(result, context, rst_mode)
 491        if context["endnotes"]:
 492            self._get_formatted_text_endnotes(result, context, rst_mode)
 493        return "".join(result)
 494
 495    def get_formated_meta(self) -> str:
 496        """Return meta informations as text, with some formatting."""
 497        result: list[str] = []
 498
 499        # Simple values
 500        def print_info(name: str, value: Any) -> None:
 501            if value:
 502                result.append(f"{name}: {value}")
 503
 504        meta = self.meta
 505        print_info("Title", meta.get_title())
 506        print_info("Subject", meta.get_subject())
 507        print_info("Language", meta.get_language())
 508        print_info("Modification date", meta.get_modification_date())
 509        print_info("Creation date", meta.get_creation_date())
 510        print_info("Initial creator", meta.get_initial_creator())
 511        print_info("Keyword", meta.get_keywords())
 512        print_info("Editing duration", meta.get_editing_duration())
 513        print_info("Editing cycles", meta.get_editing_cycles())
 514        print_info("Generator", meta.get_generator())
 515
 516        # Statistic
 517        result.append("Statistic:")
 518        statistic = meta.get_statistic()
 519        if statistic:
 520            for name, data in statistic.items():
 521                result.append(f"  - {name[5:].replace('-', ' ').capitalize()}: {data}")
 522
 523        # User defined metadata
 524        result.append("User defined metadata:")
 525        user_metadata = meta.get_user_defined_metadata()
 526        for name, data2 in user_metadata.items():
 527            result.append(f"  - {name}: {data2}")
 528
 529        # And the description
 530        print_info("Description", meta.get_description())
 531
 532        return "\n".join(result) + "\n"
 533
 534    def add_file(self, path_or_file: str | Path) -> str:
 535        """Insert a file from a path or a file-like object in the container.
 536
 537        Return the full path to reference in the content.
 538
 539        Arguments:
 540
 541            path_or_file -- str or Path or file-like
 542
 543        Return: str (URI)
 544        """
 545        if not self.container:
 546            raise ValueError("Empty Container")
 547        name = ""
 548        # Folder for added files (FIXME hard-coded and copied)
 549        manifest = self.manifest
 550        medias = manifest.get_paths()
 551        # uuid = str(uuid4())
 552
 553        if isinstance(path_or_file, (str, Path)):
 554            path = Path(path_or_file)
 555            extension = path.suffix.lower()
 556            name = f"{path.stem}{extension}"
 557            if posixpath.join("Pictures", name) in medias:
 558                name = f"{path.stem}_{uuid4()}{extension}"
 559        else:
 560            path = None
 561            name = getattr(path_or_file, "name", None)
 562            if not name:
 563                name = str(uuid4())
 564        media_type, _encoding = guess_type(name)
 565        if not media_type:
 566            media_type = "application/octet-stream"
 567        if manifest.get_media_type("Pictures/") is None:
 568            manifest.add_full_path("Pictures/")
 569        full_path = posixpath.join("Pictures", name)
 570        if path is None:
 571            self.container.set_part(full_path, path_or_file.read())
 572        else:
 573            self.container.set_part(full_path, path.read_bytes())
 574        manifest.add_full_path(full_path, media_type)
 575        return full_path
 576
 577    @property
 578    def clone(self) -> Document:
 579        """Return an exact copy of the document.
 580
 581        Return: Document
 582        """
 583        clone = object.__new__(self.__class__)
 584        for name in self.__dict__:
 585            if name == "_Document__body":
 586                setattr(clone, name, None)
 587            elif name == "_Document__xmlparts":
 588                setattr(clone, name, {})
 589            elif name == "container":
 590                if not self.container:
 591                    raise ValueError("Empty Container")
 592                setattr(clone, name, self.container.clone)
 593            else:
 594                value = deepcopy(getattr(self, name))
 595                setattr(clone, name, value)
 596        return clone
 597
 598    def save(
 599        self,
 600        target: str | Path | io.BytesIO | None = None,
 601        packaging: str = "zip",
 602        pretty: bool = False,
 603        backup: bool = False,
 604    ) -> None:
 605        """Save the document, at the same place it was opened or at the given
 606        target path. Target can also be a file-like object. It can be saved
 607        as a Zip file (default) or as files in a folder (for debugging
 608        purpose). XML parts can be pretty printed.
 609
 610        Arguments:
 611
 612            target -- str or file-like object
 613
 614            packaging -- 'zip' or 'folder'
 615
 616            pretty -- bool
 617
 618            backup -- bool
 619        """
 620        if not self.container:
 621            raise ValueError("Empty Container")
 622        # Some advertising
 623        self.meta.set_generator_default()
 624        # Synchronize data with container
 625        container = self.container
 626        for path, part in self.__xmlparts.items():
 627            if part is not None:
 628                container.set_part(path, part.serialize(pretty))
 629        # Save the container
 630        container.save(target, packaging=packaging, backup=backup)
 631
 632    @property
 633    def content(self) -> Content:
 634        content: Content | None = self.get_part(ODF_CONTENT)  # type:ignore
 635        if content is None:
 636            raise ValueError("Empty Content")
 637        return content
 638
 639    @property
 640    def styles(self) -> Styles:
 641        styles: Styles | None = self.get_part(ODF_STYLES)  # type:ignore
 642        if styles is None:
 643            raise ValueError("Empty Styles")
 644        return styles
 645
 646    # Styles over several parts
 647
 648    def get_styles(
 649        self,
 650        family: str | bytes = "",
 651        automatic: bool = False,
 652    ) -> list[Style | Element]:
 653        # compatibility with old versions:
 654
 655        if isinstance(family, bytes):
 656            family = bytes_to_str(family)
 657        return self.content.get_styles(family=family) + self.styles.get_styles(
 658            family=family, automatic=automatic
 659        )
 660
 661    def get_style(
 662        self,
 663        family: str,
 664        name_or_element: str | Style | None = None,
 665        display_name: str | None = None,
 666    ) -> Style | None:
 667        """Return the style uniquely identified by the name/family pair. If
 668        the argument is already a style object, it will return it.
 669
 670        If the name is None, the default style is fetched.
 671
 672        If the name is not the internal name but the name you gave in a
 673        desktop application, use display_name instead.
 674
 675        Arguments:
 676
 677            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
 678                      'number', 'page-layout', 'master-page'
 679
 680            name -- str or Element or None
 681
 682            display_name -- str
 683
 684        Return: Style or None if not found.
 685        """
 686        # 1. content.xml
 687        element = self.content.get_style(
 688            family, name_or_element=name_or_element, display_name=display_name
 689        )
 690        if element is not None:
 691            return element
 692        # 2. styles.xml
 693        return self.styles.get_style(
 694            family,
 695            name_or_element=name_or_element,
 696            display_name=display_name,
 697        )
 698
 699    @staticmethod
 700    def _pseudo_style_attribute(style_element: Style | Element, attribute: str) -> Any:
 701        if hasattr(style_element, attribute):
 702            return getattr(style_element, attribute)
 703        return ""
 704
 705    def _set_automatic_name(self, style: Style, family: str) -> None:
 706        """Generate a name for the new automatic style."""
 707        if not hasattr(style, "name"):
 708            # do nothing
 709            return
 710        styles = self.get_styles(family=family, automatic=True)
 711        max_index = 0
 712        for existing_style in styles:
 713            if not hasattr(existing_style, "name"):
 714                continue
 715            if not existing_style.name.startswith(AUTOMATIC_PREFIX):
 716                continue
 717            try:
 718                index = int(existing_style.name[len(AUTOMATIC_PREFIX) :])
 719            except ValueError:
 720                continue
 721            max_index = max(max_index, index)
 722
 723        style.name = f"{AUTOMATIC_PREFIX}{max_index+1}"
 724
 725    def _insert_style_get_common_styles(
 726        self,
 727        family: str,
 728        name: str,
 729    ) -> tuple[Any, Any]:
 730        style_container = self.styles.get_element("office:styles")
 731        existing = self.styles.get_style(family, name)
 732        return existing, style_container
 733
 734    def _insert_style_get_automatic_styles(
 735        self,
 736        style: Style,
 737        family: str,
 738        name: str,
 739    ) -> tuple[Any, Any]:
 740        style_container = self.content.get_element("office:automatic-styles")
 741        # A name ?
 742        if name:
 743            if hasattr(style, "name"):
 744                style.name = name
 745            existing = self.content.get_style(family, name)
 746        else:
 747            self._set_automatic_name(style, family)
 748            existing = None
 749        return existing, style_container
 750
 751    def _insert_style_get_default_styles(
 752        self,
 753        style: Style,
 754        family: str,
 755        name: str,
 756    ) -> tuple[Any, Any]:
 757        style_container = self.styles.get_element("office:styles")
 758        style.tag = "style:default-style"
 759        if name:
 760            style.del_attribute("style:name")
 761        existing = self.styles.get_style(family)
 762        return existing, style_container
 763
 764    def _insert_style_get_master_page(
 765        self,
 766        family: str,
 767        name: str,
 768    ) -> tuple[Any, Any]:
 769        style_container = self.styles.get_element("office:master-styles")
 770        existing = self.styles.get_style(family, name)
 771        return existing, style_container
 772
 773    def _insert_style_get_font_face_default(
 774        self,
 775        family: str,
 776        name: str,
 777    ) -> tuple[Any, Any]:
 778        style_container = self.styles.get_element("office:font-face-decls")
 779        existing = self.styles.get_style(family, name)
 780        return existing, style_container
 781
 782    def _insert_style_get_font_face(
 783        self,
 784        family: str,
 785        name: str,
 786    ) -> tuple[Any, Any]:
 787        style_container = self.content.get_element("office:font-face-decls")
 788        existing = self.content.get_style(family, name)
 789        return existing, style_container
 790
 791    def _insert_style_get_page_layout(
 792        self,
 793        family: str,
 794        name: str,
 795    ) -> tuple[Any, Any]:
 796        # force to automatic
 797        style_container = self.styles.get_element("office:automatic-styles")
 798        existing = self.styles.get_style(family, name)
 799        return existing, style_container
 800
 801    def _insert_style_get_draw_fill_image(
 802        self,
 803        name: str,
 804    ) -> tuple[Any, Any]:
 805        # special case for 'draw:fill-image' pseudo style
 806        # not family and style_element.__class__.__name__ == "DrawFillImage"
 807        style_container = self.styles.get_element("office:styles")
 808        existing = self.styles.get_style("", name)
 809        return existing, style_container
 810
 811    def _insert_style_standard(
 812        self,
 813        style: Style,
 814        name: str,
 815        family: str,
 816        automatic: bool,
 817        default: bool,
 818    ) -> tuple[Any, Any]:
 819        # Common style
 820        if name and automatic is False and default is False:
 821            return self._insert_style_get_common_styles(family, name)
 822        # Automatic style
 823        elif automatic is True and default is False:
 824            return self._insert_style_get_automatic_styles(style, family, name)
 825        # Default style
 826        elif automatic is False and default is True:
 827            return self._insert_style_get_default_styles(style, family, name)
 828        else:
 829            raise AttributeError("Invalid combination of arguments")
 830
 831    def insert_style(  # noqa: C901
 832        self,
 833        style: Style | str,
 834        name: str = "",
 835        automatic: bool = False,
 836        default: bool = False,
 837    ) -> Any:
 838        """Insert the given style object in the document, as required by the
 839        style family and type.
 840
 841        The style is expected to be a common style with a name. In case it
 842        was created with no name, the given can be set on the fly.
 843
 844        If automatic is True, the style will be inserted as an automatic
 845        style.
 846
 847        If default is True, the style will be inserted as a default style and
 848        would replace any existing default style of the same family. Any name
 849        or display name would be ignored.
 850
 851        Automatic and default arguments are mutually exclusive.
 852
 853        All styles can't be used as default styles. Default styles are
 854        allowed for the following families: paragraph, text, section, table,
 855        table-column, table-row, table-cell, table-page, chart, drawing-page,
 856        graphic, presentation, control and ruby.
 857
 858        Arguments:
 859
 860            style -- Style or str
 861
 862            name -- str
 863
 864            automatic -- bool
 865
 866            default -- bool
 867
 868        Return : style name -- str
 869        """
 870
 871        # if style is a str, assume it is the Style definition
 872        if isinstance(style, str):
 873            style_element: Style = Element.from_tag(style)  # type: ignore
 874        else:
 875            style_element = style
 876        if not isinstance(style_element, Element):
 877            raise TypeError(f"Unknown Style type: '{style!r}'")
 878
 879        # Get family and name
 880        family = self._pseudo_style_attribute(style_element, "family")
 881        if not name:
 882            name = self._pseudo_style_attribute(style_element, "name")
 883
 884        # Master page style
 885        if family == "master-page":
 886            existing, style_container = self._insert_style_get_master_page(family, name)
 887        # Font face declarations
 888        elif family == "font-face":
 889            if default:
 890                existing, style_container = self._insert_style_get_font_face_default(
 891                    family, name
 892                )
 893            else:
 894                existing, style_container = self._insert_style_get_font_face(
 895                    family, name
 896                )
 897        # page layout style
 898        elif family == "page-layout":
 899            existing, style_container = self._insert_style_get_page_layout(family, name)
 900        # Common style
 901        elif family in FAMILY_ODF_STD or family in {"number"}:
 902            existing, style_container = self._insert_style_standard(
 903                style_element, name, family, automatic, default
 904            )
 905        elif not family and style_element.__class__.__name__ == "DrawFillImage":
 906            # special case for 'draw:fill-image' pseudo style
 907            existing, style_container = self._insert_style_get_draw_fill_image(name)
 908        # Invalid style
 909        else:
 910            raise ValueError(
 911                "Invalid style: "
 912                f"{style_element}, tag:{style_element.tag}, family:{family}"
 913            )
 914
 915        # Insert it!
 916        if existing is not None:
 917            style_container.delete(existing)
 918        style_container.append(style_element)
 919        return self._pseudo_style_attribute(style_element, "name")
 920
 921    def get_styled_elements(self, name: str = "") -> list[Element]:
 922        """Brute-force to find paragraphs, tables, etc. using the given style
 923        name (or all by default).
 924
 925        Arguments:
 926
 927            name -- str
 928
 929        Return: list
 930        """
 931        # Header, footer, etc. have styles too
 932        return self.content.root.get_styled_elements(
 933            name
 934        ) + self.styles.root.get_styled_elements(name)
 935
 936    def show_styles(
 937        self,
 938        automatic: bool = True,
 939        common: bool = True,
 940        properties: bool = False,
 941    ) -> str:
 942        infos = []
 943        for style in self.get_styles():
 944            try:
 945                name = style.name  # type: ignore
 946            except AttributeError:
 947                print("--------------")
 948                print(style.__class__)
 949                print(style.serialize())
 950                raise
 951            if style.__class__.__name__ == "DrawFillImage":
 952                family = ""
 953            else:
 954                family = str(style.family)  # type: ignore
 955            parent = style.parent
 956            is_auto = parent and parent.tag == "office:automatic-styles"
 957            if is_auto and automatic is False or not is_auto and common is False:
 958                continue
 959            is_used = bool(self.get_styled_elements(name))
 960            infos.append(
 961                {
 962                    "type": "auto  " if is_auto else "common",
 963                    "used": "y" if is_used else "n",
 964                    "family": family,
 965                    "parent": self._pseudo_style_attribute(style, "parent_style") or "",
 966                    "name": name or "",
 967                    "display_name": self._pseudo_style_attribute(style, "display_name")
 968                    or "",
 969                    "properties": style.get_properties() if properties else None,  # type: ignore
 970                }
 971            )
 972        if not infos:
 973            return ""
 974        # Sort by family and name
 975        infos.sort(key=itemgetter("family", "name"))
 976        # Show common and used first
 977        infos.sort(key=itemgetter("type", "used"), reverse=True)
 978        max_family = str(max([len(x["family"]) for x in infos]))  # type: ignore
 979        max_parent = str(max([len(x["parent"]) for x in infos]))  # type: ignore
 980        formater = (
 981            "%(type)s used:%(used)s family:%(family)-0"
 982            + max_family
 983            + "s parent:%(parent)-0"
 984            + max_parent
 985            + "s name:%(name)s"
 986        )
 987        output = []
 988        for info in infos:
 989            line = formater % info
 990            if info["display_name"]:
 991                line += " display_name:" + info["display_name"]  # type: ignore
 992            output.append(line)
 993            if info["properties"]:
 994                for name, value in info["properties"].items():  # type: ignore
 995                    output.append(f"   - {name}: {value}")
 996        output.append("")
 997        return "\n".join(output)
 998
 999    def delete_styles(self) -> int:
1000        """Remove all style information from content and all styles.
1001
1002        Return: number of deleted styles
1003        """
1004        # First remove references to styles
1005        for element in self.get_styled_elements():
1006            for attribute in (
1007                "text:style-name",
1008                "draw:style-name",
1009                "draw:text-style-name",
1010                "table:style-name",
1011                "style:page-layout-name",
1012            ):
1013                try:
1014                    element.del_attribute(attribute)
1015                except KeyError:
1016                    continue
1017        # Then remove supposedly orphaned styles
1018        deleted = 0
1019        for style in self.get_styles():
1020            if style.name is None:  # type: ignore
1021                # Don't delete default styles
1022                continue
1023            # elif type(style) is odf_master_page:
1024            #    # Don't suppress header and footer, just styling was removed
1025            #    continue
1026            style.delete()
1027            deleted += 1
1028        return deleted
1029
1030    def merge_styles_from(self, document: Document) -> None:
1031        """Copy all the styles of a document into ourself.
1032
1033        Styles with the same type and name will be replaced, so only unique
1034        styles will be preserved.
1035        """
1036        manifest = self.manifest
1037        document_manifest = document.manifest
1038        for style in document.get_styles():
1039            tagname = style.tag
1040            family = self._pseudo_style_attribute(style, "family")
1041            stylename = style.name  # type: ignore
1042            container = style.parent
1043            container_name = container.tag  # type: ignore
1044            partname = container.parent.tag  # type: ignore
1045            # The destination part
1046            if partname == "office:document-styles":
1047                part: Content | Styles = self.styles
1048            elif partname == "office:document-content":
1049                part = self.content
1050            else:
1051                raise NotImplementedError(partname)
1052            # Implemented containers
1053            if container_name not in {
1054                "office:styles",
1055                "office:automatic-styles",
1056                "office:master-styles",
1057                "office:font-face-decls",
1058            }:
1059                raise NotImplementedError(container_name)
1060            dest = part.get_element(f"//{container_name}")
1061            # Implemented style types
1062            # if tagname not in registered_styles:
1063            #    raise NotImplementedError(tagname)
1064            duplicate = part.get_style(family, stylename)
1065            if duplicate is not None:
1066                duplicate.delete()
1067            dest.append(style)
1068            # Copy images from the header/footer
1069            if tagname == "style:master-page":
1070                query = "descendant::draw:image"
1071                for image in style.get_elements(query):
1072                    url = image.url  # type: ignore
1073                    part_url = document.get_part(url)
1074                    # Manually add the part to keep the name
1075                    self.set_part(url, part_url)  # type: ignore
1076                    media_type = document_manifest.get_media_type(url)
1077                    manifest.add_full_path(url, media_type)  # type: ignore
1078            # Copy images from the fill-image
1079            elif tagname == "draw:fill-image":
1080                url = style.url  # type: ignore
1081                part_url = document.get_part(url)
1082                self.set_part(url, part_url)  # type: ignore
1083                media_type = document_manifest.get_media_type(url)
1084                manifest.add_full_path(url, media_type)  # type: ignore
1085
1086    def add_page_break_style(self) -> None:
1087        """Ensure that the document contains the style required for a manual page break.
1088
1089        Then a manual page break can be added to the document with:
1090            from paragraph import PageBreak
1091            ...
1092            document.body.append(PageBreak())
1093
1094        Note: this style uses the property 'fo:break-after', another
1095        possibility could be the property 'fo:break-before'
1096        """
1097        if existing := self.get_style(  # noqa: SIM102
1098            family="paragraph",
1099            name_or_element="odfdopagebreak",
1100        ):
1101            if properties := existing.get_properties():  # noqa: SIM102
1102                if properties["fo:break-after"] == "page":
1103                    return
1104        style = (
1105            '<style:style style:family="paragraph" style:parent-style-name="Standard" '
1106            'style:name="odfdopagebreak">'
1107            '<style:paragraph-properties fo:break-after="page"/></style:style>'
1108        )
1109        self.insert_style(style, automatic=False)

Abstraction of the ODF document.

To create a new Document, several possibilities:

- Document() or Document("text") -> an "empty" document of type text
- Document("spreadsheet") -> an "empty" document of type spreadsheet
- Document("presentation") -> an "empty" document of type presentation
- Document("drawing") -> an "empty" document of type drawing

Meaning of “empty”: these documents are copies of the default
templates documents provided with this library, which, as templates,
are not really empty. It may be useful to clear the newly created
document: document.body.clear(), or adjust meta informations like
description or default language: document.meta.set_language('fr-FR')

If the argument is not a known template type, or is a Path, Document(file) will load the content of the ODF file.

To explicitly create a document from a custom template, use the Document.new(path) method whose argument is the path to the template file.

Document( target: str | bytes | pathlib.Path | Container | _io.BytesIO | None = 'text')
178    def __init__(
179        self,
180        target: str | bytes | Path | Container | io.BytesIO | None = "text",
181    ) -> None:
182        # Cache of XML parts
183        self.__xmlparts: dict[str, XmlPart] = {}
184        # Cache of the body
185        self.__body: Element | None = None
186        self.container: Container | None = None
187        if isinstance(target, bytes):
188            # eager conversion
189            target = bytes_to_str(target)
190        if target is None:
191            # empty document, you probably don't wnat this.
192            self.container = Container()
193            return
194        if isinstance(target, Path):
195            # let's assume we open a container on existing file
196            self.container = Container(target)
197            return
198        if isinstance(target, Container):
199            # special internal case, use an existing container
200            self.container = target
201            return
202        if isinstance(target, str):
203            if target in ODF_TEMPLATES:
204                # assuming a new document from templates
205                self.container = container_from_template(target)
206                return
207            # let's assume we open a container on existing file
208            self.container = Container(target)
209            return
210        if isinstance(target, io.BytesIO):
211            self.container = Container(target)
212            return
213        raise TypeError(f"Unknown Document source type: '{target!r}'")
container: Container | None
@classmethod
def new( cls, template: str | pathlib.Path | _io.BytesIO = 'text') -> Document:
224    @classmethod
225    def new(cls, template: str | Path | io.BytesIO = "text") -> Document:
226        """Create a Document from a template.
227
228        The template argument is expected to be the path to a ODF template.
229
230        Arguments:
231
232            template -- str or Path or file-like (io.BytesIO)
233
234        Return : ODF document -- Document
235        """
236        container = container_from_template(template)
237        return cls(container)

Create a Document from a template.

The template argument is expected to be the path to a ODF template.

Arguments:

template -- str or Path or file-like (io.BytesIO)

Return : ODF document -- Document

path: pathlib.Path | None
241    @property
242    def path(self) -> Path | None:
243        """Shortcut to Document.Container.path."""
244        if not self.container:
245            return None
246        return self.container.path

Shortcut to Document.Container.path.

def get_parts(self) -> list[str]:
257    def get_parts(self) -> list[str]:
258        """Return available part names with path inside the archive, e.g.
259        ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
260        """
261        if not self.container:
262            raise ValueError("Empty Container")
263        return self.container.get_parts()

Return available part names with path inside the archive, e.g. ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']

def get_part(self, path: str) -> XmlPart | str | bytes | None:
265    def get_part(self, path: str) -> XmlPart | str | bytes | None:
266        """Return the bytes of the given part. The path is relative to the
267        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
268
269        'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts
270        to the real path, e.g. content.xml, and return a dedicated object with
271        its own API.
272
273        path formated as URI, so always use '/' separator
274        """
275        if not self.container:
276            raise ValueError("Empty Container")
277        # "./ObjectReplacements/Object 1"
278        path = path.lstrip("./")
279        path = _get_part_path(path)
280        cls = _get_part_class(path)
281        # Raw bytes
282        if cls is None:
283            return self.container.get_part(path)
284        # XML part
285        part = self.__xmlparts.get(path)
286        if part is None:
287            self.__xmlparts[path] = part = cls(path, self.container)
288        return part

Return the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".

'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts to the real path, e.g. content.xml, and return a dedicated object with its own API.

path formated as URI, so always use '/' separator

def set_part(self, path: str, data: bytes) -> None:
290    def set_part(self, path: str, data: bytes) -> None:
291        """Set the bytes of the given part. The path is relative to the
292        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
293
294        path formated as URI, so always use '/' separator
295        """
296        if not self.container:
297            raise ValueError("Empty Container")
298        # "./ObjectReplacements/Object 1"
299        path = path.lstrip("./")
300        path = _get_part_path(path)
301        cls = _get_part_class(path)
302        # XML part overwritten
303        if cls is not None:
304            with suppress(KeyError):
305                self.__xmlparts[path]
306        self.container.set_part(path, data)

Set the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".

path formated as URI, so always use '/' separator

def del_part(self, path: str) -> None:
308    def del_part(self, path: str) -> None:
309        """Mark a part for deletion. The path is relative to the archive,
310        e.g. "Pictures/1003200258912EB1C3.jpg"
311        """
312        if not self.container:
313            raise ValueError("Empty Container")
314        path = _get_part_path(path)
315        cls = _get_part_class(path)
316        if path == ODF_MANIFEST or cls is not None:
317            raise ValueError(f"part '{path}' is mandatory")
318        self.container.del_part(path)

Mark a part for deletion. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg"

mimetype: str
320    @property
321    def mimetype(self) -> str:
322        if not self.container:
323            raise ValueError("Empty Container")
324        return self.container.mimetype
def get_type(self) -> str:
332    def get_type(self) -> str:
333        """Get the ODF type (also called class) of this document.
334
335        Return: 'chart', 'database', 'formula', 'graphics',
336            'graphics-template', 'image', 'presentation',
337            'presentation-template', 'spreadsheet', 'spreadsheet-template',
338            'text', 'text-master', 'text-template' or 'text-web'
339        """
340        # The mimetype must be with the form:
341        # application/vnd.oasis.opendocument.text
342
343        # Isolate and return the last part
344        return self.mimetype.rsplit(".", 1)[-1]

Get the ODF type (also called class) of this document.

Return: 'chart', 'database', 'formula', 'graphics', 'graphics-template', 'image', 'presentation', 'presentation-template', 'spreadsheet', 'spreadsheet-template', 'text', 'text-master', 'text-template' or 'text-web'

body: Element
346    @property
347    def body(self) -> Element:
348        """Return the body element of the content part, where actual content
349        is stored.
350        """
351        if self.__body is None:
352            self.__body = self.content.body
353        return self.__body

Return the body element of the content part, where actual content is stored.

meta: Meta
355    @property
356    def meta(self) -> Meta:
357        """Return the meta part (meta.xml) of the document, where meta data
358        are stored."""
359        metadata = self.get_part(ODF_META)
360        if metadata is None or not isinstance(metadata, Meta):
361            raise ValueError("Empty Meta")
362        return metadata

Return the meta part (meta.xml) of the document, where meta data are stored.

manifest: Manifest
364    @property
365    def manifest(self) -> Manifest:
366        """Return the manifest part (manifest.xml) of the document."""
367        manifest = self.get_part(ODF_MANIFEST)
368        if manifest is None or not isinstance(manifest, Manifest):
369            raise ValueError("Empty Manifest")
370        return manifest

Return the manifest part (manifest.xml) of the document.

def get_formatted_text(self, rst_mode: bool = False) -> str:
445    def get_formatted_text(self, rst_mode: bool = False) -> str:
446        """Return content as text, with some formatting."""
447        # For the moment, only "type='text'"
448        doc_type = self.get_type()
449        if doc_type == "spreadsheet":
450            return self._tables_csv()
451        if doc_type in {
452            "text",
453            "text-template",
454            "presentation",
455            "presentation-template",
456        }:
457            return self._formatted_text(rst_mode)
458        raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")

Return content as text, with some formatting.

def get_formated_meta(self) -> str:
495    def get_formated_meta(self) -> str:
496        """Return meta informations as text, with some formatting."""
497        result: list[str] = []
498
499        # Simple values
500        def print_info(name: str, value: Any) -> None:
501            if value:
502                result.append(f"{name}: {value}")
503
504        meta = self.meta
505        print_info("Title", meta.get_title())
506        print_info("Subject", meta.get_subject())
507        print_info("Language", meta.get_language())
508        print_info("Modification date", meta.get_modification_date())
509        print_info("Creation date", meta.get_creation_date())
510        print_info("Initial creator", meta.get_initial_creator())
511        print_info("Keyword", meta.get_keywords())
512        print_info("Editing duration", meta.get_editing_duration())
513        print_info("Editing cycles", meta.get_editing_cycles())
514        print_info("Generator", meta.get_generator())
515
516        # Statistic
517        result.append("Statistic:")
518        statistic = meta.get_statistic()
519        if statistic:
520            for name, data in statistic.items():
521                result.append(f"  - {name[5:].replace('-', ' ').capitalize()}: {data}")
522
523        # User defined metadata
524        result.append("User defined metadata:")
525        user_metadata = meta.get_user_defined_metadata()
526        for name, data2 in user_metadata.items():
527            result.append(f"  - {name}: {data2}")
528
529        # And the description
530        print_info("Description", meta.get_description())
531
532        return "\n".join(result) + "\n"

Return meta informations as text, with some formatting.

def add_file(self, path_or_file: str | pathlib.Path) -> str:
534    def add_file(self, path_or_file: str | Path) -> str:
535        """Insert a file from a path or a file-like object in the container.
536
537        Return the full path to reference in the content.
538
539        Arguments:
540
541            path_or_file -- str or Path or file-like
542
543        Return: str (URI)
544        """
545        if not self.container:
546            raise ValueError("Empty Container")
547        name = ""
548        # Folder for added files (FIXME hard-coded and copied)
549        manifest = self.manifest
550        medias = manifest.get_paths()
551        # uuid = str(uuid4())
552
553        if isinstance(path_or_file, (str, Path)):
554            path = Path(path_or_file)
555            extension = path.suffix.lower()
556            name = f"{path.stem}{extension}"
557            if posixpath.join("Pictures", name) in medias:
558                name = f"{path.stem}_{uuid4()}{extension}"
559        else:
560            path = None
561            name = getattr(path_or_file, "name", None)
562            if not name:
563                name = str(uuid4())
564        media_type, _encoding = guess_type(name)
565        if not media_type:
566            media_type = "application/octet-stream"
567        if manifest.get_media_type("Pictures/") is None:
568            manifest.add_full_path("Pictures/")
569        full_path = posixpath.join("Pictures", name)
570        if path is None:
571            self.container.set_part(full_path, path_or_file.read())
572        else:
573            self.container.set_part(full_path, path.read_bytes())
574        manifest.add_full_path(full_path, media_type)
575        return full_path

Insert a file from a path or a file-like object in the container.

Return the full path to reference in the content.

Arguments:

path_or_file -- str or Path or file-like

Return: str (URI)

clone: Document
577    @property
578    def clone(self) -> Document:
579        """Return an exact copy of the document.
580
581        Return: Document
582        """
583        clone = object.__new__(self.__class__)
584        for name in self.__dict__:
585            if name == "_Document__body":
586                setattr(clone, name, None)
587            elif name == "_Document__xmlparts":
588                setattr(clone, name, {})
589            elif name == "container":
590                if not self.container:
591                    raise ValueError("Empty Container")
592                setattr(clone, name, self.container.clone)
593            else:
594                value = deepcopy(getattr(self, name))
595                setattr(clone, name, value)
596        return clone

Return an exact copy of the document.

Return: Document

def save( self, target: str | pathlib.Path | _io.BytesIO | None = None, packaging: str = 'zip', pretty: bool = False, backup: bool = False) -> None:
598    def save(
599        self,
600        target: str | Path | io.BytesIO | None = None,
601        packaging: str = "zip",
602        pretty: bool = False,
603        backup: bool = False,
604    ) -> None:
605        """Save the document, at the same place it was opened or at the given
606        target path. Target can also be a file-like object. It can be saved
607        as a Zip file (default) or as files in a folder (for debugging
608        purpose). XML parts can be pretty printed.
609
610        Arguments:
611
612            target -- str or file-like object
613
614            packaging -- 'zip' or 'folder'
615
616            pretty -- bool
617
618            backup -- bool
619        """
620        if not self.container:
621            raise ValueError("Empty Container")
622        # Some advertising
623        self.meta.set_generator_default()
624        # Synchronize data with container
625        container = self.container
626        for path, part in self.__xmlparts.items():
627            if part is not None:
628                container.set_part(path, part.serialize(pretty))
629        # Save the container
630        container.save(target, packaging=packaging, backup=backup)

Save the document, at the same place it was opened or at the given target path. Target can also be a file-like object. It can be saved as a Zip file (default) or as files in a folder (for debugging purpose). XML parts can be pretty printed.

Arguments:

target -- str or file-like object

packaging -- 'zip' or 'folder'

pretty -- bool

backup -- bool
content: Content
632    @property
633    def content(self) -> Content:
634        content: Content | None = self.get_part(ODF_CONTENT)  # type:ignore
635        if content is None:
636            raise ValueError("Empty Content")
637        return content
styles: Styles
639    @property
640    def styles(self) -> Styles:
641        styles: Styles | None = self.get_part(ODF_STYLES)  # type:ignore
642        if styles is None:
643            raise ValueError("Empty Styles")
644        return styles
def get_styles( self, family: str | bytes = '', automatic: bool = False) -> list[Style | Element]:
648    def get_styles(
649        self,
650        family: str | bytes = "",
651        automatic: bool = False,
652    ) -> list[Style | Element]:
653        # compatibility with old versions:
654
655        if isinstance(family, bytes):
656            family = bytes_to_str(family)
657        return self.content.get_styles(family=family) + self.styles.get_styles(
658            family=family, automatic=automatic
659        )
def get_style( self, family: str, name_or_element: str | Style | None = None, display_name: str | None = None) -> Style | None:
661    def get_style(
662        self,
663        family: str,
664        name_or_element: str | Style | None = None,
665        display_name: str | None = None,
666    ) -> Style | None:
667        """Return the style uniquely identified by the name/family pair. If
668        the argument is already a style object, it will return it.
669
670        If the name is None, the default style is fetched.
671
672        If the name is not the internal name but the name you gave in a
673        desktop application, use display_name instead.
674
675        Arguments:
676
677            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
678                      'number', 'page-layout', 'master-page'
679
680            name -- str or Element or None
681
682            display_name -- str
683
684        Return: Style or None if not found.
685        """
686        # 1. content.xml
687        element = self.content.get_style(
688            family, name_or_element=name_or_element, display_name=display_name
689        )
690        if element is not None:
691            return element
692        # 2. styles.xml
693        return self.styles.get_style(
694            family,
695            name_or_element=name_or_element,
696            display_name=display_name,
697        )

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in a desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text',  'graphic', 'table', 'list',
          'number', 'page-layout', 'master-page'

name -- str or Element or None

display_name -- str

Return: Style or None if not found.

def insert_style( self, style: Style | str, name: str = '', automatic: bool = False, default: bool = False) -> Any:
831    def insert_style(  # noqa: C901
832        self,
833        style: Style | str,
834        name: str = "",
835        automatic: bool = False,
836        default: bool = False,
837    ) -> Any:
838        """Insert the given style object in the document, as required by the
839        style family and type.
840
841        The style is expected to be a common style with a name. In case it
842        was created with no name, the given can be set on the fly.
843
844        If automatic is True, the style will be inserted as an automatic
845        style.
846
847        If default is True, the style will be inserted as a default style and
848        would replace any existing default style of the same family. Any name
849        or display name would be ignored.
850
851        Automatic and default arguments are mutually exclusive.
852
853        All styles can't be used as default styles. Default styles are
854        allowed for the following families: paragraph, text, section, table,
855        table-column, table-row, table-cell, table-page, chart, drawing-page,
856        graphic, presentation, control and ruby.
857
858        Arguments:
859
860            style -- Style or str
861
862            name -- str
863
864            automatic -- bool
865
866            default -- bool
867
868        Return : style name -- str
869        """
870
871        # if style is a str, assume it is the Style definition
872        if isinstance(style, str):
873            style_element: Style = Element.from_tag(style)  # type: ignore
874        else:
875            style_element = style
876        if not isinstance(style_element, Element):
877            raise TypeError(f"Unknown Style type: '{style!r}'")
878
879        # Get family and name
880        family = self._pseudo_style_attribute(style_element, "family")
881        if not name:
882            name = self._pseudo_style_attribute(style_element, "name")
883
884        # Master page style
885        if family == "master-page":
886            existing, style_container = self._insert_style_get_master_page(family, name)
887        # Font face declarations
888        elif family == "font-face":
889            if default:
890                existing, style_container = self._insert_style_get_font_face_default(
891                    family, name
892                )
893            else:
894                existing, style_container = self._insert_style_get_font_face(
895                    family, name
896                )
897        # page layout style
898        elif family == "page-layout":
899            existing, style_container = self._insert_style_get_page_layout(family, name)
900        # Common style
901        elif family in FAMILY_ODF_STD or family in {"number"}:
902            existing, style_container = self._insert_style_standard(
903                style_element, name, family, automatic, default
904            )
905        elif not family and style_element.__class__.__name__ == "DrawFillImage":
906            # special case for 'draw:fill-image' pseudo style
907            existing, style_container = self._insert_style_get_draw_fill_image(name)
908        # Invalid style
909        else:
910            raise ValueError(
911                "Invalid style: "
912                f"{style_element}, tag:{style_element.tag}, family:{family}"
913            )
914
915        # Insert it!
916        if existing is not None:
917            style_container.delete(existing)
918        style_container.append(style_element)
919        return self._pseudo_style_attribute(style_element, "name")

Insert the given style object in the document, as required by the style family and type.

The style is expected to be a common style with a name. In case it was created with no name, the given can be set on the fly.

If automatic is True, the style will be inserted as an automatic style.

If default is True, the style will be inserted as a default style and would replace any existing default style of the same family. Any name or display name would be ignored.

Automatic and default arguments are mutually exclusive.

All styles can't be used as default styles. Default styles are allowed for the following families: paragraph, text, section, table, table-column, table-row, table-cell, table-page, chart, drawing-page, graphic, presentation, control and ruby.

Arguments:

style -- Style or str

name -- str

automatic -- bool

default -- bool

Return : style name -- str

def get_styled_elements(self, name: str = '') -> list[Element]:
921    def get_styled_elements(self, name: str = "") -> list[Element]:
922        """Brute-force to find paragraphs, tables, etc. using the given style
923        name (or all by default).
924
925        Arguments:
926
927            name -- str
928
929        Return: list
930        """
931        # Header, footer, etc. have styles too
932        return self.content.root.get_styled_elements(
933            name
934        ) + self.styles.root.get_styled_elements(name)

Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).

Arguments:

name -- str

Return: list

def show_styles( self, automatic: bool = True, common: bool = True, properties: bool = False) -> str:
936    def show_styles(
937        self,
938        automatic: bool = True,
939        common: bool = True,
940        properties: bool = False,
941    ) -> str:
942        infos = []
943        for style in self.get_styles():
944            try:
945                name = style.name  # type: ignore
946            except AttributeError:
947                print("--------------")
948                print(style.__class__)
949                print(style.serialize())
950                raise
951            if style.__class__.__name__ == "DrawFillImage":
952                family = ""
953            else:
954                family = str(style.family)  # type: ignore
955            parent = style.parent
956            is_auto = parent and parent.tag == "office:automatic-styles"
957            if is_auto and automatic is False or not is_auto and common is False:
958                continue
959            is_used = bool(self.get_styled_elements(name))
960            infos.append(
961                {
962                    "type": "auto  " if is_auto else "common",
963                    "used": "y" if is_used else "n",
964                    "family": family,
965                    "parent": self._pseudo_style_attribute(style, "parent_style") or "",
966                    "name": name or "",
967                    "display_name": self._pseudo_style_attribute(style, "display_name")
968                    or "",
969                    "properties": style.get_properties() if properties else None,  # type: ignore
970                }
971            )
972        if not infos:
973            return ""
974        # Sort by family and name
975        infos.sort(key=itemgetter("family", "name"))
976        # Show common and used first
977        infos.sort(key=itemgetter("type", "used"), reverse=True)
978        max_family = str(max([len(x["family"]) for x in infos]))  # type: ignore
979        max_parent = str(max([len(x["parent"]) for x in infos]))  # type: ignore
980        formater = (
981            "%(type)s used:%(used)s family:%(family)-0"
982            + max_family
983            + "s parent:%(parent)-0"
984            + max_parent
985            + "s name:%(name)s"
986        )
987        output = []
988        for info in infos:
989            line = formater % info
990            if info["display_name"]:
991                line += " display_name:" + info["display_name"]  # type: ignore
992            output.append(line)
993            if info["properties"]:
994                for name, value in info["properties"].items():  # type: ignore
995                    output.append(f"   - {name}: {value}")
996        output.append("")
997        return "\n".join(output)
def delete_styles(self) -> int:
 999    def delete_styles(self) -> int:
1000        """Remove all style information from content and all styles.
1001
1002        Return: number of deleted styles
1003        """
1004        # First remove references to styles
1005        for element in self.get_styled_elements():
1006            for attribute in (
1007                "text:style-name",
1008                "draw:style-name",
1009                "draw:text-style-name",
1010                "table:style-name",
1011                "style:page-layout-name",
1012            ):
1013                try:
1014                    element.del_attribute(attribute)
1015                except KeyError:
1016                    continue
1017        # Then remove supposedly orphaned styles
1018        deleted = 0
1019        for style in self.get_styles():
1020            if style.name is None:  # type: ignore
1021                # Don't delete default styles
1022                continue
1023            # elif type(style) is odf_master_page:
1024            #    # Don't suppress header and footer, just styling was removed
1025            #    continue
1026            style.delete()
1027            deleted += 1
1028        return deleted

Remove all style information from content and all styles.

Return: number of deleted styles

def merge_styles_from(self, document: Document) -> None:
1030    def merge_styles_from(self, document: Document) -> None:
1031        """Copy all the styles of a document into ourself.
1032
1033        Styles with the same type and name will be replaced, so only unique
1034        styles will be preserved.
1035        """
1036        manifest = self.manifest
1037        document_manifest = document.manifest
1038        for style in document.get_styles():
1039            tagname = style.tag
1040            family = self._pseudo_style_attribute(style, "family")
1041            stylename = style.name  # type: ignore
1042            container = style.parent
1043            container_name = container.tag  # type: ignore
1044            partname = container.parent.tag  # type: ignore
1045            # The destination part
1046            if partname == "office:document-styles":
1047                part: Content | Styles = self.styles
1048            elif partname == "office:document-content":
1049                part = self.content
1050            else:
1051                raise NotImplementedError(partname)
1052            # Implemented containers
1053            if container_name not in {
1054                "office:styles",
1055                "office:automatic-styles",
1056                "office:master-styles",
1057                "office:font-face-decls",
1058            }:
1059                raise NotImplementedError(container_name)
1060            dest = part.get_element(f"//{container_name}")
1061            # Implemented style types
1062            # if tagname not in registered_styles:
1063            #    raise NotImplementedError(tagname)
1064            duplicate = part.get_style(family, stylename)
1065            if duplicate is not None:
1066                duplicate.delete()
1067            dest.append(style)
1068            # Copy images from the header/footer
1069            if tagname == "style:master-page":
1070                query = "descendant::draw:image"
1071                for image in style.get_elements(query):
1072                    url = image.url  # type: ignore
1073                    part_url = document.get_part(url)
1074                    # Manually add the part to keep the name
1075                    self.set_part(url, part_url)  # type: ignore
1076                    media_type = document_manifest.get_media_type(url)
1077                    manifest.add_full_path(url, media_type)  # type: ignore
1078            # Copy images from the fill-image
1079            elif tagname == "draw:fill-image":
1080                url = style.url  # type: ignore
1081                part_url = document.get_part(url)
1082                self.set_part(url, part_url)  # type: ignore
1083                media_type = document_manifest.get_media_type(url)
1084                manifest.add_full_path(url, media_type)  # type: ignore

Copy all the styles of a document into ourself.

Styles with the same type and name will be replaced, so only unique styles will be preserved.

def add_page_break_style(self) -> None:
1086    def add_page_break_style(self) -> None:
1087        """Ensure that the document contains the style required for a manual page break.
1088
1089        Then a manual page break can be added to the document with:
1090            from paragraph import PageBreak
1091            ...
1092            document.body.append(PageBreak())
1093
1094        Note: this style uses the property 'fo:break-after', another
1095        possibility could be the property 'fo:break-before'
1096        """
1097        if existing := self.get_style(  # noqa: SIM102
1098            family="paragraph",
1099            name_or_element="odfdopagebreak",
1100        ):
1101            if properties := existing.get_properties():  # noqa: SIM102
1102                if properties["fo:break-after"] == "page":
1103                    return
1104        style = (
1105            '<style:style style:family="paragraph" style:parent-style-name="Standard" '
1106            'style:name="odfdopagebreak">'
1107            '<style:paragraph-properties fo:break-after="page"/></style:style>'
1108        )
1109        self.insert_style(style, automatic=False)

Ensure that the document contains the style required for a manual page break.

Then a manual page break can be added to the document with: from paragraph import PageBreak ... document.body.append(PageBreak())

Note: this style uses the property 'fo:break-after', another possibility could be the property 'fo:break-before'

class DrawFillImage(odfdo.DrawImage):
 85class DrawFillImage(DrawImage):
 86    _tag = "draw:fill-image"
 87    _properties: tuple[PropDef, ...] = (
 88        PropDef("display_name", "draw:display-name"),
 89        PropDef("name", "draw:name"),
 90        PropDef("height", "svg:height"),
 91        PropDef("width", "svg:width"),
 92    )
 93
 94    def __init__(
 95        self,
 96        name: str | None = None,
 97        display_name: str | None = None,
 98        height: str | None = None,
 99        width: str | None = None,
100        **kwargs: Any,
101    ) -> None:
102        """The "draw:fill-image" element specifies a link to a bitmap
103        resource. Fill image are not available as automatic styles.
104        The "draw:fill-image" element is usable within the following element:
105        "office:styles"
106
107        Arguments:
108
109            name -- str
110
111            display_name -- str
112
113            height -- str
114
115            width -- str
116        """
117        super().__init__(**kwargs)
118        if self._do_init:
119            self.name = name
120            self.display_name = display_name
121            self.height = height
122            self.width = width

The "draw:image" element represents an image. An image can be either:

  • A link to an external resource or
  • Embedded in the document (Not implemented in this version)

Warning: image elements must be stored in a frame "draw:frame", see Frame().

DrawFillImage( name: str | None = None, display_name: str | None = None, height: str | None = None, width: str | None = None, **kwargs: Any)
 94    def __init__(
 95        self,
 96        name: str | None = None,
 97        display_name: str | None = None,
 98        height: str | None = None,
 99        width: str | None = None,
100        **kwargs: Any,
101    ) -> None:
102        """The "draw:fill-image" element specifies a link to a bitmap
103        resource. Fill image are not available as automatic styles.
104        The "draw:fill-image" element is usable within the following element:
105        "office:styles"
106
107        Arguments:
108
109            name -- str
110
111            display_name -- str
112
113            height -- str
114
115            width -- str
116        """
117        super().__init__(**kwargs)
118        if self._do_init:
119            self.name = name
120            self.display_name = display_name
121            self.height = height
122            self.width = width

The "draw:fill-image" element specifies a link to a bitmap resource. Fill image are not available as automatic styles. The "draw:fill-image" element is usable within the following element: "office:styles"

Arguments:

name -- str

display_name -- str

height -- str

width -- str
display_name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
height: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
width: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
DrawImage
url
type
show
actuate
filter_name
class DrawGroup(odfdo.Element, odfdo.frame.AnchorMix, odfdo.frame.ZMix, odfdo.frame.PosMix):
315class DrawGroup(Element, AnchorMix, ZMix, PosMix):
316    """The DrawGroup "draw:g" element represents a group of drawing shapes.
317
318    Warning: implementation is currently minimal.
319
320    Drawing shapes contained by a "draw:g" element that is itself
321    contained by a "draw:a" element, act as hyperlinks using the
322    xlink:href attribute of the containing "draw:a" element. If the
323    included drawing shapes are themselves contained within "draw:a"
324    elements, then the xlink:href attributes of those "draw:a" elements
325    act as the hyperlink information for the shapes they contain.
326
327    The "draw:g" element has the following attributes: draw:caption-id,
328    draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index,
329    presentation:class-names, presentation:style-name, svg:y,
330    table:end-cell-address, table:end-x, table:end-y,
331    table:table-background, text:anchor-page-number, text:anchor-type,
332    and xml:id.
333
334    The "draw:g" element has the following child elements: "dr3d:scene",
335    "draw:a", "draw:caption", "draw:circle", "draw:connector",
336    "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame",
337    "draw:g", "draw:glue-point", "draw:line", "draw:measure",
338    "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline",
339    "draw:rect", "draw:regular-polygon", "office:event-listeners",
340    "svg:desc" and "svg:title".
341    """
342
343    _tag = "draw:g"
344    _properties: tuple[PropDef, ...] = (
345        PropDef("draw_id", "draw:id"),
346        PropDef("caption_id", "draw:caption-id"),
347        PropDef("draw_class_names", "draw:class-names"),
348        PropDef("name", "draw:name"),
349        PropDef("style", "draw:style-name"),
350        # ('z_index', 'draw:z-index'),
351        PropDef("presentation_class_names", "presentation:class-names"),
352        PropDef("presentation_style", "presentation:style-name"),
353        PropDef("table_end_cell", "table:end-cell-address"),
354        PropDef("table_end_x", "table:end-x"),
355        PropDef("table_end_y", "table:end-y"),
356        PropDef("table_background", "table:table-background"),
357        # ('anchor_page', 'text:anchor-page-number'),
358        # ('anchor_type', 'text:anchor-type'),
359        PropDef("xml_id", "xml:id"),
360        PropDef("pos_x", "svg:x"),
361        PropDef("pos_y", "svg:y"),
362    )
363
364    def __init__(
365        self,
366        name: str | None = None,
367        draw_id: str | None = None,
368        style: str | None = None,
369        position: tuple | None = None,
370        z_index: int = 0,
371        anchor_type: str | None = None,
372        anchor_page: int | None = None,
373        presentation_style: str | None = None,
374        **kwargs: Any,
375    ) -> None:
376        super().__init__(**kwargs)
377        if self._do_init:
378            if z_index is not None:
379                self.z_index = z_index
380            if name:
381                self.name = name
382            if draw_id is not None:
383                self.draw_id = draw_id
384            if style is not None:
385                self.style = style
386            if position is not None:
387                self.position = position
388            if anchor_type:
389                self.anchor_type = anchor_type
390            if anchor_page is not None:
391                self.anchor_page = anchor_page
392            if presentation_style is not None:
393                self.presentation_style = presentation_style

The DrawGroup "draw:g" element represents a group of drawing shapes.

Warning: implementation is currently minimal.

Drawing shapes contained by a "draw:g" element that is itself contained by a "draw:a" element, act as hyperlinks using the xlink:href attribute of the containing "draw:a" element. If the included drawing shapes are themselves contained within "draw:a" elements, then the xlink:href attributes of those "draw:a" elements act as the hyperlink information for the shapes they contain.

The "draw:g" element has the following attributes: draw:caption-id, draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, presentation:class-names, presentation:style-name, svg:y, table:end-cell-address, table:end-x, table:end-y, table:table-background, text:anchor-page-number, text:anchor-type, and xml:id.

The "draw:g" element has the following child elements: "dr3d:scene", "draw:a", "draw:caption", "draw:circle", "draw:connector", "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", "draw:g", "draw:glue-point", "draw:line", "draw:measure", "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", "draw:rect", "draw:regular-polygon", "office:event-listeners", "svg:desc" and "svg:title".

DrawGroup( name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, z_index: int = 0, anchor_type: str | None = None, anchor_page: int | None = None, presentation_style: str | None = None, **kwargs: Any)
364    def __init__(
365        self,
366        name: str | None = None,
367        draw_id: str | None = None,
368        style: str | None = None,
369        position: tuple | None = None,
370        z_index: int = 0,
371        anchor_type: str | None = None,
372        anchor_page: int | None = None,
373        presentation_style: str | None = None,
374        **kwargs: Any,
375    ) -> None:
376        super().__init__(**kwargs)
377        if self._do_init:
378            if z_index is not None:
379                self.z_index = z_index
380            if name:
381                self.name = name
382            if draw_id is not None:
383                self.draw_id = draw_id
384            if style is not None:
385                self.style = style
386            if position is not None:
387                self.position = position
388            if anchor_type:
389                self.anchor_type = anchor_type
390            if anchor_page is not None:
391                self.anchor_page = anchor_page
392            if presentation_style is not None:
393                self.presentation_style = presentation_style
draw_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
caption_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
draw_class_names: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
presentation_class_names: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
presentation_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
table_end_cell: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
table_end_x: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
table_end_y: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
table_background: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
xml_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
pos_x: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
pos_y: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.AnchorMix
ANCHOR_VALUE_CHOICE
anchor_type
anchor_page
odfdo.frame.ZMix
z_index
odfdo.frame.PosMix
position
class DrawImage(odfdo.Element):
31class DrawImage(Element):
32    """The "draw:image" element represents an image. An image can be
33    either:
34    - A link to an external resource or
35    - Embedded in the document (Not implemented in this version)
36
37    Warning: image elements must be stored in a frame "draw:frame",
38    see Frame().
39    """
40
41    _tag = "draw:image"
42    _properties: tuple[PropDef, ...] = (
43        PropDef("url", "xlink:href"),
44        PropDef("type", "xlink:type"),
45        PropDef("show", "xlink:show"),
46        PropDef("actuate", "xlink:actuate"),
47        PropDef("filter_name", "draw:filter-name"),
48    )
49
50    def __init__(
51        self,
52        url: str = "",
53        xlink_type: str = "simple",
54        show: str = "embed",
55        actuate: str = "onLoad",
56        filter_name: str | None = None,
57        **kwargs: Any,
58    ) -> None:
59        """Initialisation of an DrawImage.
60
61        Arguments:
62
63            url -- str
64
65            type -- str
66
67            show -- str
68
69            actuate -- str
70
71            filter_name -- str
72        """
73        super().__init__(**kwargs)
74        if self._do_init:
75            self.url = url
76            self.type = xlink_type
77            self.show = show
78            self.actuate = actuate
79            self.filter_name = filter_name

The "draw:image" element represents an image. An image can be either:

  • A link to an external resource or
  • Embedded in the document (Not implemented in this version)

Warning: image elements must be stored in a frame "draw:frame", see Frame().

DrawImage( url: str = '', xlink_type: str = 'simple', show: str = 'embed', actuate: str = 'onLoad', filter_name: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        url: str = "",
53        xlink_type: str = "simple",
54        show: str = "embed",
55        actuate: str = "onLoad",
56        filter_name: str | None = None,
57        **kwargs: Any,
58    ) -> None:
59        """Initialisation of an DrawImage.
60
61        Arguments:
62
63            url -- str
64
65            type -- str
66
67            show -- str
68
69            actuate -- str
70
71            filter_name -- str
72        """
73        super().__init__(**kwargs)
74        if self._do_init:
75            self.url = url
76            self.type = xlink_type
77            self.show = show
78            self.actuate = actuate
79            self.filter_name = filter_name

Initialisation of an DrawImage.

Arguments:

url -- str

type -- str

show -- str

actuate -- str

filter_name -- str
url: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
show: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
actuate: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
filter_name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class DrawPage(odfdo.Element):
 33class DrawPage(Element):
 34    """ODF draw page "draw:page", for pages of presentation and drawings."""
 35
 36    _tag = "draw:page"
 37    _properties = (
 38        PropDef("name", "draw:name"),
 39        PropDef("draw_id", "draw:id"),
 40        PropDef("master_page", "draw:master-page-name"),
 41        PropDef(
 42            "presentation_page_layout", "presentation:presentation-page-layout-name"
 43        ),
 44        PropDef("style", "draw:style-name"),
 45    )
 46
 47    def __init__(
 48        self,
 49        draw_id: str | None = None,
 50        name: str | None = None,
 51        master_page: str | None = None,
 52        presentation_page_layout: str | None = None,
 53        style: str | None = None,
 54        **kwargs: Any,
 55    ) -> None:
 56        """
 57        Arguments:
 58
 59            draw_id -- str
 60
 61            name -- str
 62
 63            master_page -- str
 64
 65            presentation_page_layout -- str
 66
 67            style -- str
 68        """
 69        super().__init__(**kwargs)
 70        if self._do_init:
 71            if draw_id:
 72                self.draw_id = draw_id
 73            if name:
 74                self.name = name
 75            if master_page:
 76                self.master_page = master_page
 77            if presentation_page_layout:
 78                self.presentation_page_layout = presentation_page_layout
 79            if style:
 80                self.style = style
 81
 82    def set_transition(
 83        self,
 84        smil_type: str,
 85        subtype: str | None = None,
 86        dur: str = "2s",
 87    ) -> None:
 88        # Create the new animation
 89        anim_page = AnimPar(presentation_node_type="timing-root")
 90        anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin")
 91        transition = AnimTransFilter(
 92            smil_dur=dur, smil_type=smil_type, smil_subtype=subtype
 93        )
 94        anim_page.append(anim_begin)
 95        anim_begin.append(transition)
 96
 97        # Replace when already a transition:
 98        #   anim:seq => After the frame's transition
 99        #   cf page 349 of OpenDocument-v1.0-os.pdf
100        #   Conclusion: We must delete the first child 'anim:par'
101        existing = self.get_element("anim:par")
102        if existing:
103            self.delete(existing)
104        self.append(anim_page)
105
106    def get_shapes(self) -> list[Element]:
107        query = "(descendant::" + "|descendant::".join(registered_shapes) + ")"
108        return self.get_elements(query)
109
110    def get_formatted_text(self, context: dict | None = None) -> str:
111        result: list[str] = []
112        for child in self.children:
113            if child.tag == "presentation:notes":
114                # No need for an advanced odf_notes.get_formatted_text()
115                # because the text seems to be only contained in paragraphs
116                # and frames, that we already handle
117                for sub_child in child.children:
118                    result.append(sub_child.get_formatted_text(context))
119                result.append("\n")
120            result.append(child.get_formatted_text(context))
121        result.append("\n")
122        return "".join(result)

ODF draw page "draw:page", for pages of presentation and drawings.

DrawPage( draw_id: str | None = None, name: str | None = None, master_page: str | None = None, presentation_page_layout: str | None = None, style: str | None = None, **kwargs: Any)
47    def __init__(
48        self,
49        draw_id: str | None = None,
50        name: str | None = None,
51        master_page: str | None = None,
52        presentation_page_layout: str | None = None,
53        style: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        """
57        Arguments:
58
59            draw_id -- str
60
61            name -- str
62
63            master_page -- str
64
65            presentation_page_layout -- str
66
67            style -- str
68        """
69        super().__init__(**kwargs)
70        if self._do_init:
71            if draw_id:
72                self.draw_id = draw_id
73            if name:
74                self.name = name
75            if master_page:
76                self.master_page = master_page
77            if presentation_page_layout:
78                self.presentation_page_layout = presentation_page_layout
79            if style:
80                self.style = style

Arguments:

draw_id -- str

name -- str

master_page -- str

presentation_page_layout -- str

style -- str
def set_transition( self, smil_type: str, subtype: str | None = None, dur: str = '2s') -> None:
 82    def set_transition(
 83        self,
 84        smil_type: str,
 85        subtype: str | None = None,
 86        dur: str = "2s",
 87    ) -> None:
 88        # Create the new animation
 89        anim_page = AnimPar(presentation_node_type="timing-root")
 90        anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin")
 91        transition = AnimTransFilter(
 92            smil_dur=dur, smil_type=smil_type, smil_subtype=subtype
 93        )
 94        anim_page.append(anim_begin)
 95        anim_begin.append(transition)
 96
 97        # Replace when already a transition:
 98        #   anim:seq => After the frame's transition
 99        #   cf page 349 of OpenDocument-v1.0-os.pdf
100        #   Conclusion: We must delete the first child 'anim:par'
101        existing = self.get_element("anim:par")
102        if existing:
103            self.delete(existing)
104        self.append(anim_page)
def get_shapes(self) -> list[Element]:
106    def get_shapes(self) -> list[Element]:
107        query = "(descendant::" + "|descendant::".join(registered_shapes) + ")"
108        return self.get_elements(query)
def get_formatted_text(self, context: dict | None = None) -> str:
110    def get_formatted_text(self, context: dict | None = None) -> str:
111        result: list[str] = []
112        for child in self.children:
113            if child.tag == "presentation:notes":
114                # No need for an advanced odf_notes.get_formatted_text()
115                # because the text seems to be only contained in paragraphs
116                # and frames, that we already handle
117                for sub_child in child.children:
118                    result.append(sub_child.get_formatted_text(context))
119                result.append("\n")
120            result.append(child.get_formatted_text(context))
121        result.append("\n")
122        return "".join(result)

This function should return a beautiful version of the text.

name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
draw_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
master_page: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
presentation_page_layout: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Element(odfdo.utils.cached_element.CachedElement):
 304class Element(CachedElement):
 305    """Super class of all ODF classes.
 306
 307    Representation of an XML element. Abstraction of the XML library behind.
 308    """
 309
 310    _tag: str = ""
 311    _caching: bool = False
 312    _properties: tuple[PropDef, ...] = ()
 313
 314    def __init__(self, **kwargs: Any) -> None:
 315        tag_or_elem = kwargs.pop("tag_or_elem", None)
 316        if tag_or_elem is None:
 317            # Instance for newly created object: create new lxml element and
 318            # continue by subclass __init__
 319            # If the tag key word exists, make a custom element
 320            self._do_init = True
 321            tag = kwargs.pop("tag", self._tag)
 322            self.__element = self.make_etree_element(tag)
 323        else:
 324            # called with an existing lxml element, sould be a result of
 325            # from_tag() casting, do not execute the subclass __init__
 326            if not isinstance(tag_or_elem, _Element):
 327                raise TypeError(f'"{type(tag_or_elem)}" is not an element node')
 328            self._do_init = False
 329            self.__element = tag_or_elem
 330
 331    def __repr__(self) -> str:
 332        return f"<{self.__class__.__name__} tag={self.tag}>"
 333
 334    def __str__(self) -> str:
 335        return self.text_recursive
 336
 337    @classmethod
 338    def from_tag(cls, tag_or_elem: str | _Element) -> Element:
 339        """Element class and subclass factory.
 340
 341        Turn an lxml Element or ODF string tag into an ODF XML Element
 342        of the relevant class.
 343
 344        Arguments:
 345
 346            tag_or_elem -- ODF str tag or lxml.Element
 347
 348        Return: Element (or subclass) instance
 349        """
 350        if isinstance(tag_or_elem, str):
 351            # assume the argument is a prefix:name tag
 352            elem = cls.make_etree_element(tag_or_elem)
 353        else:
 354            elem = tag_or_elem
 355        klass = _class_registry.get(elem.tag, cls)
 356        return klass(tag_or_elem=elem)
 357
 358    @classmethod
 359    def from_tag_for_clone(
 360        cls: type,
 361        tree_element: _Element,
 362        cache: tuple | None,
 363    ) -> Element:
 364        tag = to_str(tree_element.tag)
 365        klass = _class_registry.get(tag, cls)
 366        element = klass(tag_or_elem=tree_element)
 367        if cache and element._caching:
 368            element._tmap = cache[0]
 369            element._cmap = cache[1]
 370            if len(cache) == 3:
 371                element._rmap = cache[2]
 372        return element
 373
 374    @staticmethod
 375    def make_etree_element(tag: str) -> _Element:
 376        if not isinstance(tag, str):
 377            raise TypeError(f"Tag is not str: {tag!r}")
 378        tag = tag.strip()
 379        if not tag:
 380            raise ValueError("Tag is empty")
 381        if "<" not in tag:
 382            # Qualified name
 383            # XXX don't build the element from scratch or lxml will pollute with
 384            # repeated namespace declarations
 385            tag = f"<{tag}/>"
 386        # XML fragment
 387        root = fromstring(NAMESPACES_XML % str_to_bytes(tag))
 388        return root[0]
 389
 390    @staticmethod
 391    def _generic_attrib_getter(attr_name: str, family: str | None = None) -> Callable:
 392        name = _get_lxml_tag(attr_name)
 393
 394        def getter(self: Element) -> str | bool | None:
 395            try:
 396                if family and self.family != family:  # type: ignore
 397                    return None
 398            except AttributeError:
 399                return None
 400            value = self.__element.get(name)
 401            if value is None:
 402                return None
 403            elif value in ("true", "false"):
 404                return Boolean.decode(value)
 405            return str(value)
 406
 407        return getter
 408
 409    @staticmethod
 410    def _generic_attrib_setter(attr_name: str, family: str | None = None) -> Callable:
 411        name = _get_lxml_tag(attr_name)
 412
 413        def setter(self: Element, value: Any) -> None:
 414            try:
 415                if family and self.family != family:  # type: ignore
 416                    return None
 417            except AttributeError:
 418                return None
 419            if value is None:
 420                with contextlib.suppress(KeyError):
 421                    del self.__element.attrib[name]
 422                return
 423            if isinstance(value, bool):
 424                value = Boolean.encode(value)
 425            self.__element.set(name, str(value))
 426
 427        return setter
 428
 429    @classmethod
 430    def _define_attribut_property(cls: type[Element]) -> None:
 431        for prop in cls._properties:
 432            setattr(
 433                cls,
 434                prop.name,
 435                property(
 436                    cls._generic_attrib_getter(prop.attr, prop.family or None),
 437                    cls._generic_attrib_setter(prop.attr, prop.family or None),
 438                    None,
 439                    f"Get/set the attribute {prop.attr}",
 440                ),
 441            )
 442
 443    @staticmethod
 444    def _make_before_regex(
 445        before: str | None,
 446        after: str | None,
 447    ) -> re.Pattern:
 448        # 1) before xor after is not None
 449        if before is not None:
 450            return re.compile(before)
 451        else:
 452            if after is None:
 453                raise ValueError("Both 'before' and 'after' are None")
 454            return re.compile(after)
 455
 456    @staticmethod
 457    def _search_negative_position(
 458        xpath_result: list,
 459        regex: re.Pattern,
 460    ) -> tuple[str, re.Match]:
 461        # Found the last text that matches the regex
 462        text = None
 463        for a_text in xpath_result:
 464            if regex.search(str(a_text)) is not None:
 465                text = a_text
 466        if text is None:
 467            raise ValueError(f"Text not found: '{xpath_result}'")
 468        if not isinstance(text, str):
 469            raise TypeError(f"Text not found or text not of type str: '{text}'")
 470        return text, list(regex.finditer(text))[-1]
 471
 472    @staticmethod
 473    def _search_positive_position(
 474        xpath_result: list,
 475        regex: re.Pattern,
 476        position: int,
 477    ) -> tuple[str, re.Match]:
 478        # Found the last text that matches the regex
 479        count = 0
 480        for text in xpath_result:
 481            found_nb = len(regex.findall(str(text)))
 482            if found_nb + count >= position + 1:
 483                break
 484            count += found_nb
 485        else:
 486            raise ValueError(f"Text not found: '{xpath_result}'")
 487        if not isinstance(text, str):
 488            raise TypeError(f"Text not found or text not of type str: '{text}'")
 489        return text, list(regex.finditer(text))[position - count]
 490
 491    def _insert_before_after(
 492        self,
 493        current: _Element,
 494        element: _Element,
 495        before: str | None,
 496        after: str | None,
 497        position: int,
 498        xpath_text: XPath,
 499    ) -> tuple[int, str]:
 500        regex = self._make_before_regex(before, after)
 501        xpath_result = xpath_text(current)
 502        if not isinstance(xpath_result, list):
 503            raise TypeError("Bad XPath result")
 504        # position = -1
 505        if position < 0:
 506            text, sre = self._search_negative_position(xpath_result, regex)
 507        # position >= 0
 508        else:
 509            text, sre = self._search_positive_position(xpath_result, regex, position)
 510        # Compute pos
 511        if before is None:
 512            pos = sre.end()
 513        else:
 514            pos = sre.start()
 515        return pos, text
 516
 517    def _insert_find_text(
 518        self,
 519        current: _Element,
 520        element: _Element,
 521        before: str | None,
 522        after: str | None,
 523        position: int,
 524        xpath_text: XPath,
 525    ) -> tuple[int, str]:
 526        # Find the text
 527        xpath_result = xpath_text(current)
 528        if not isinstance(xpath_result, list):
 529            raise TypeError("Bad XPath result")
 530        count = 0
 531        for text in xpath_result:
 532            if not isinstance(text, str):
 533                continue
 534            found_nb = len(text)
 535            if found_nb + count >= position:
 536                break
 537            count += found_nb
 538        else:
 539            raise ValueError("Text not found")
 540        # We insert before the character
 541        pos = position - count
 542        return pos, text
 543
 544    def _insert(
 545        self,
 546        element: Element,
 547        before: str | None = None,
 548        after: str | None = None,
 549        position: int = 0,
 550        main_text: bool = False,
 551    ) -> None:
 552        """Insert an element before or after the characters in the text which
 553        match the regex before/after.
 554
 555        When the regex matches more of one part of the text, position can be
 556        set to choice which part must be used. If before and after are None,
 557        we use only position that is the number of characters. If position is
 558        positive and before=after=None, we insert before the position
 559        character. But if position=-1, we insert after the last character.
 560
 561
 562        Arguments:
 563
 564        element -- Element
 565
 566        before -- str regex
 567
 568        after -- str regex
 569
 570        position -- int
 571        """
 572        # not implemented: if main_text is True, filter out the annotations texts in computation.
 573        current = self.__element
 574        xelement = element.__element
 575
 576        if main_text:
 577            xpath_text = _xpath_text_main_descendant
 578        else:
 579            xpath_text = _xpath_text_descendant
 580
 581        # 1) before xor after is not None
 582        if (before is not None) ^ (after is not None):
 583            pos, text = self._insert_before_after(
 584                current,
 585                xelement,
 586                before,
 587                after,
 588                position,
 589                xpath_text,
 590            )
 591        # 2) before=after=None => only with position
 592        elif before is None and after is None:
 593            # Hack if position is negative => quickly
 594            if position < 0:
 595                current.append(xelement)
 596                return
 597            pos, text = self._insert_find_text(
 598                current,
 599                xelement,
 600                before,
 601                after,
 602                position,
 603                xpath_text,
 604            )
 605        else:
 606            raise ValueError("bad combination of arguments")
 607
 608        # Compute new texts
 609        text_before = text[:pos] if text[:pos] else None
 610        text_after = text[pos:] if text[pos:] else None
 611
 612        # Insert!
 613        parent = text.getparent()  # type: ignore
 614        if text.is_text:  # type: ignore
 615            parent.text = text_before
 616            element.tail = text_after
 617            parent.insert(0, xelement)
 618        else:
 619            parent.addnext(xelement)
 620            parent.tail = text_before
 621            element.tail = text_after
 622
 623    def _insert_between(  # noqa: C901
 624        self,
 625        element: Element,
 626        from_: str,
 627        to: str,
 628    ) -> None:
 629        """Insert the given empty element to wrap the text beginning with
 630        "from_" and ending with "to".
 631
 632        Example 1: '<p>toto tata titi</p>
 633
 634        We want to insert a link around "tata".
 635
 636        Result 1: '<p>toto <a>tata</a> titi</p>
 637
 638        Example 2: '<p><span>toto</span> tata titi</p>
 639
 640        We want to insert a link around "tata".
 641
 642        Result 2: '<p><span>toto</span> <a>tata</a> titi</p>
 643
 644        Example 3: '<p>toto <span> tata </span> titi</p>'
 645
 646        We want to insert a link from "tata" to "titi" included.
 647
 648        Result 3: '<p>toto <span> </span>'
 649                  '<a><span>tata </span> titi</a></p>'
 650
 651        Example 4: '<p>toto <span>tata titi</span> tutu</p>'
 652
 653        We want to insert a link from "titi" to "tutu"
 654
 655        Result 4: '<p>toto <span>tata </span><a><span>titi</span></a>'
 656                  '<a> tutu</a></p>'
 657
 658        Example 5: '<p>toto <span>tata titi</span> '
 659                   '<span>tutu tyty</span></p>'
 660
 661        We want to insert a link from "titi" to "tutu"
 662
 663        Result 5: '<p>toto <span>tata </span><a><span>titi</span><a> '
 664                  '<a> <span>tutu</span></a><span> tyty</span></p>'
 665        """
 666        current = self.__element
 667        wrapper = element.__element
 668
 669        xpath_result = _xpath_text_descendant(current)
 670        if not isinstance(xpath_result, list):
 671            raise TypeError("Bad XPath result")
 672
 673        for text in xpath_result:
 674            if not isinstance(text, str):
 675                raise TypeError("Text not found or text not of type str")
 676            if from_ not in text:
 677                continue
 678            from_index = text.index(from_)
 679            text_before = text[:from_index]
 680            text_after = text[from_index:]
 681            from_container = text.getparent()  # type: ignore
 682            if not isinstance(from_container, _Element):
 683                raise TypeError("Bad XPath result")
 684            # Include from_index to match a single word
 685            to_index = text.find(to, from_index)
 686            if to_index >= 0:
 687                # Simple case: "from" and "to" in the same element
 688                to_end = to_index + len(to)
 689                if text.is_text:  # type: ignore
 690                    from_container.text = text_before
 691                    wrapper.text = text[to_index:to_end]
 692                    wrapper.tail = text[to_end:]
 693                    from_container.insert(0, wrapper)
 694                else:
 695                    from_container.tail = text_before
 696                    wrapper.text = text[to_index:to_end]
 697                    wrapper.tail = text[to_end:]
 698                    parent = from_container.getparent()
 699                    index = parent.index(from_container)  # type: ignore
 700                    parent.insert(index + 1, wrapper)  # type: ignore
 701                return
 702            else:
 703                # Exit to the second part where we search for the end text
 704                break
 705        else:
 706            raise ValueError("Start text not found")
 707
 708        # The container is split in two
 709        container2 = deepcopy(from_container)
 710        if text.is_text:  # type: ignore
 711            from_container.text = text_before
 712            from_container.tail = None
 713            container2.text = text_after
 714            from_container.tail = None
 715        else:
 716            from_container.tail = text_before
 717            container2.tail = text_after
 718        # Stack the copy into the surrounding element
 719        wrapper.append(container2)
 720        parent = from_container.getparent()
 721        index = parent.index(from_container)  # type: ignore
 722        parent.insert(index + 1, wrapper)  # type: ignore
 723
 724        xpath_result = _xpath_text_descendant(wrapper)
 725        if not isinstance(xpath_result, list):
 726            raise TypeError("Bad XPath result")
 727
 728        for text in xpath_result:
 729            if not isinstance(text, str):
 730                raise TypeError("Text not found or text not of type str")
 731            if to not in text:
 732                continue
 733            to_end = text.index(to) + len(to)
 734            text_before = text[:to_end]
 735            text_after = text[to_end:]
 736            container_to = text.getparent()  # type: ignore
 737            if not isinstance(container_to, _Element):
 738                raise TypeError("Bad XPath result")
 739            if text.is_text:  # type: ignore
 740                container_to.text = text_before
 741                container_to.tail = text_after
 742            else:
 743                container_to.tail = text_before
 744                next_one = container_to.getnext()
 745                if next_one is None:
 746                    next_one = container_to.getparent()
 747                next_one.tail = text_after  # type: ignore
 748            return
 749        raise ValueError("End text not found")
 750
 751    @property
 752    def tag(self) -> str:
 753        """Get/set the underlying xml tag with the given qualified name.
 754
 755        Warning: direct change of tag does not change the element class.
 756
 757        Arguments:
 758
 759            qname -- str (e.g. "text:span")
 760        """
 761        return _get_prefixed_name(self.__element.tag)
 762
 763    @tag.setter
 764    def tag(self, qname: str) -> None:
 765        self.__element.tag = _get_lxml_tag(qname)
 766
 767    def elements_repeated_sequence(
 768        self,
 769        xpath_instance: XPath,
 770        name: str,
 771    ) -> list[tuple[int, int]]:
 772        """Utility method for table module."""
 773        lxml_tag = _get_lxml_tag_or_name(name)
 774        element = self.__element
 775        sub_elements = xpath_instance(element)
 776        if not isinstance(sub_elements, list):
 777            raise TypeError("Bad XPath result.")
 778        result: list[tuple[int, int]] = []
 779        idx = -1
 780        for sub_element in sub_elements:
 781            if not isinstance(sub_element, _Element):
 782                continue
 783            idx += 1
 784            value = sub_element.get(lxml_tag)
 785            if value is None:
 786                result.append((idx, 1))
 787                continue
 788            try:
 789                int_value = int(value)
 790            except ValueError:
 791                int_value = 1
 792            result.append((idx, max(int_value, 1)))
 793        return result
 794
 795    def get_elements(self, xpath_query: XPath | str) -> list[Element]:
 796        cache: tuple | None = None
 797        element = self.__element
 798        if isinstance(xpath_query, str):
 799            new_xpath_query = xpath_compile(xpath_query)
 800            result = new_xpath_query(element)
 801        else:
 802            result = xpath_query(element)
 803        if not isinstance(result, list):
 804            raise TypeError("Bad XPath result")
 805
 806        if hasattr(self, "_tmap"):
 807            if hasattr(self, "_rmap"):
 808                cache = (self._tmap, self._cmap, self._rmap)
 809            else:
 810                cache = (self._tmap, self._cmap)
 811        return [
 812            Element.from_tag_for_clone(e, cache)
 813            for e in result
 814            if isinstance(e, _Element)
 815        ]
 816
 817    # fixme : need original get_element as wrapper of get_elements
 818
 819    def get_element(self, xpath_query: XPath | str) -> Element | None:
 820        element = self.__element
 821        result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES)
 822        if result:
 823            return Element.from_tag(result[0])  # type:ignore
 824        return None
 825
 826    def _get_element_idx(self, xpath_query: XPath | str, idx: int) -> Element | None:
 827        element = self.__element
 828        result = element.xpath(f"({xpath_query})[{idx + 1}]", namespaces=ODF_NAMESPACES)
 829        if result:
 830            return Element.from_tag(result[0])  # type:ignore
 831        return None
 832
 833    def _get_element_idx2(self, xpath_instance: XPath, idx: int) -> Element | None:
 834        element = self.__element
 835        result = xpath_instance(element, idx=idx + 1)
 836        if result:
 837            return Element.from_tag(result[0])  # type:ignore
 838        return None
 839
 840    @property
 841    def attributes(self) -> dict[str, str]:
 842        return {
 843            _get_prefixed_name(str(key)): str(value)
 844            for key, value in self.__element.attrib.items()
 845        }
 846
 847    def get_attribute(self, name: str) -> str | bool | None:
 848        """Return the attribute value as type str | bool | None."""
 849        element = self.__element
 850        lxml_tag = _get_lxml_tag_or_name(name)
 851        value = element.get(lxml_tag)
 852        if value is None:
 853            return None
 854        elif value in ("true", "false"):
 855            return Boolean.decode(value)
 856        return str(value)
 857
 858    def get_attribute_integer(self, name: str) -> int | None:
 859        """Return either the attribute as type int, or None."""
 860        element = self.__element
 861        lxml_tag = _get_lxml_tag_or_name(name)
 862        value = element.get(lxml_tag)
 863        if value is None:
 864            return None
 865        try:
 866            return int(value)
 867        except ValueError:
 868            return None
 869
 870    def get_attribute_string(self, name: str) -> str | None:
 871        """Return either the attribute as type str, or None."""
 872        element = self.__element
 873        lxml_tag = _get_lxml_tag_or_name(name)
 874        value = element.get(lxml_tag)
 875        if value is None:
 876            return None
 877        return str(value)
 878
 879    def set_attribute(self, name: str, value: bool | str | None) -> None:
 880        element = self.__element
 881        lxml_tag = _get_lxml_tag_or_name(name)
 882        if isinstance(value, bool):
 883            value = Boolean.encode(value)
 884        elif value is None:
 885            with contextlib.suppress(KeyError):
 886                del element.attrib[lxml_tag]
 887            return
 888        element.set(lxml_tag, str(value))
 889
 890    def set_style_attribute(self, name: str, value: Element | str) -> None:
 891        """Shortcut to accept a style object as a value."""
 892        if isinstance(value, Element):
 893            value = str(value.name)  # type:ignore
 894        return self.set_attribute(name, value)
 895
 896    def del_attribute(self, name: str) -> None:
 897        element = self.__element
 898        lxml_tag = _get_lxml_tag_or_name(name)
 899        del element.attrib[lxml_tag]
 900
 901    @property
 902    def text(self) -> str:
 903        """Get / set the text content of the element."""
 904        return self.__element.text or ""
 905
 906    @text.setter
 907    def text(self, text: str | None) -> None:
 908        if text is None:
 909            text = ""
 910        try:
 911            self.__element.text = text
 912        except TypeError as e:
 913            raise TypeError(f'Str type expected: "{type(text)}"') from e
 914
 915    @property
 916    def text_recursive(self) -> str:
 917        return "".join(str(x) for x in self.__element.itertext())
 918
 919    @property
 920    def tail(self) -> str | None:
 921        """Get / set the text immediately following the element."""
 922        return self.__element.tail
 923
 924    @tail.setter
 925    def tail(self, text: str | None) -> None:
 926        self.__element.tail = text or ""
 927
 928    def search(self, pattern: str) -> int | None:
 929        """Return the first position of the pattern in the text content of
 930        the element, or None if not found.
 931
 932        Python regular expression syntax applies.
 933
 934        Arguments:
 935
 936            pattern -- str
 937
 938        Return: int or None
 939        """
 940        match = re.search(pattern, self.text_recursive)
 941        if match is None:
 942            return None
 943        return match.start()
 944
 945    def match(self, pattern: str) -> bool:
 946        """return True if the pattern is found one or more times anywhere in
 947        the text content of the element.
 948
 949        Python regular expression syntax applies.
 950
 951        Arguments:
 952
 953            pattern -- str
 954
 955        Return: bool
 956        """
 957        return self.search(pattern) is not None
 958
 959    def replace(self, pattern: str, new: str | None = None) -> int:
 960        """Replace the pattern with the given text, or delete if text is an
 961        empty string, and return the number of replacements. By default, only
 962        return the number of occurences that would be replaced.
 963
 964        It cannot replace patterns found across several element, like a word
 965        split into two consecutive spans.
 966
 967        Python regular expression syntax applies.
 968
 969        Arguments:
 970
 971            pattern -- str
 972
 973            new -- str
 974
 975        Return: int
 976        """
 977        if not isinstance(pattern, str):
 978            # Fail properly if the pattern is an non-ascii bytestring
 979            pattern = str(pattern)
 980        cpattern = re.compile(pattern)
 981        count = 0
 982        for text in self.xpath("descendant::text()"):
 983            if new is None:
 984                count += len(cpattern.findall(str(text)))
 985            else:
 986                new_text, number = cpattern.subn(new, str(text))
 987                container = text.parent
 988                if text.is_text():  # type: ignore
 989                    container.text = new_text  # type: ignore
 990                else:
 991                    container.tail = new_text  # type: ignore
 992                count += number
 993        return count
 994
 995    @property
 996    def root(self) -> Element:
 997        element = self.__element
 998        tree = element.getroottree()
 999        root = tree.getroot()
1000        return Element.from_tag(root)
1001
1002    @property
1003    def parent(self) -> Element | None:
1004        element = self.__element
1005        parent = element.getparent()
1006        if parent is None:
1007            # Already at root
1008            return None
1009        return Element.from_tag(parent)
1010
1011    @property
1012    def is_bound(self) -> bool:
1013        return self.parent is not None
1014
1015    # def get_next_sibling(self):
1016    #     element = self.__element
1017    #     next_one = element.getnext()
1018    #     if next_one is None:
1019    #         return None
1020    #     return Element.from_tag(next_one)
1021    #
1022    # def get_prev_sibling(self):
1023    #     element = self.__element
1024    #     prev = element.getprevious()
1025    #     if prev is None:
1026    #         return None
1027    #     return Element.from_tag(prev)
1028
1029    @property
1030    def children(self) -> list[Element]:
1031        element = self.__element
1032        return [
1033            Element.from_tag(e)
1034            for e in element.iterchildren()
1035            if isinstance(e, _Element)
1036        ]
1037
1038    def index(self, child: Element) -> int:
1039        """Return the position of the child in this element.
1040
1041        Inspired by lxml
1042        """
1043        return self.__element.index(child.__element)
1044
1045    @property
1046    def text_content(self) -> str:
1047        """Get / set the text of the embedded paragraph, including embeded
1048        annotations, cells...
1049
1050        Set create a paragraph if missing
1051        """
1052        return "\n".join(
1053            child.text_recursive for child in self.get_elements("descendant::text:p")
1054        )
1055
1056    @text_content.setter
1057    def text_content(self, text: str | None) -> None:
1058        paragraphs = self.get_elements("text:p")
1059        if not paragraphs:
1060            # E.g., text:p in draw:text-box in draw:frame
1061            paragraphs = self.get_elements("*/text:p")
1062        if paragraphs:
1063            paragraph = paragraphs.pop(0)
1064            for obsolete in paragraphs:
1065                obsolete.delete()
1066        else:
1067            paragraph = Element.from_tag("text:p")
1068            self.insert(paragraph, FIRST_CHILD)
1069        # As "text_content" returned all text nodes, "text_content"
1070        # will overwrite all text nodes and children that may contain them
1071        element = paragraph.__element
1072        # Clear but the attributes
1073        del element[:]
1074        element.text = text
1075
1076    def _erase_text_content(self) -> None:
1077        paragraphs = self.get_elements("text:p")
1078        if not paragraphs:
1079            # E.g., text:p in draw:text-box in draw:frame
1080            paragraphs = self.get_elements("*/text:p")
1081        if paragraphs:
1082            paragraphs.pop(0)
1083            for obsolete in paragraphs:
1084                obsolete.delete()
1085
1086    def is_empty(self) -> bool:
1087        """Check if the element is empty : no text, no children, no tail.
1088
1089        Return: Boolean
1090        """
1091        element = self.__element
1092        if element.tail is not None:
1093            return False
1094        if element.text is not None:
1095            return False
1096        if list(element.iterchildren()):
1097            return False
1098        return True
1099
1100    def _get_successor(self, target: Element) -> tuple[Element | None, Element | None]:
1101        element = self.__element
1102        next_one = element.getnext()
1103        if next_one is not None:
1104            return Element.from_tag(next_one), target
1105        parent = self.parent
1106        if parent is None:
1107            return None, None
1108        return parent._get_successor(target.parent)  # type:ignore
1109
1110    def _get_between_base(  # noqa:C901
1111        self,
1112        tag1: Element,
1113        tag2: Element,
1114    ) -> list[Element]:
1115        def find_any_id(elem: Element) -> tuple[str, str, str]:
1116            elem_tag = elem.tag
1117            for attribute in (
1118                "text:id",
1119                "text:change-id",
1120                "text:name",
1121                "office:name",
1122                "text:ref-name",
1123                "xml:id",
1124            ):
1125                idx = elem.get_attribute(attribute)
1126                if idx is not None:
1127                    return elem_tag, attribute, str(idx)
1128            raise ValueError(f"No Id found in {elem.serialize()}")
1129
1130        def common_ancestor(
1131            tag1: str,
1132            attr1: str,
1133            val1: str,
1134            tag2: str,
1135            attr2: str,
1136            val2: str,
1137        ) -> Element | None:
1138            root = self.root
1139            request1 = f'descendant::{tag1}[@{attr1}="{val1}"]'
1140            request2 = f'descendant::{tag2}[@{attr2}="{val2}"]'
1141            ancestor = root.xpath(request1)[0]
1142            if ancestor is None:
1143                return None
1144            while True:
1145                # print "up",
1146                new_ancestor = ancestor.parent
1147                if new_ancestor is None:
1148                    return None
1149                has_tag2 = new_ancestor.xpath(request2)
1150                ancestor = new_ancestor
1151                if not has_tag2:
1152                    continue
1153                # print 'found'
1154                break
1155            # print up.serialize()
1156            return ancestor
1157
1158        elem1_tag, elem1_attr, elem1_val = find_any_id(tag1)
1159        elem2_tag, elem2_attr, elem2_val = find_any_id(tag2)
1160        ancestor_result = common_ancestor(
1161            elem1_tag,
1162            elem1_attr,
1163            elem1_val,
1164            elem2_tag,
1165            elem2_attr,
1166            elem2_val,
1167        )
1168        if ancestor_result is None:
1169            raise RuntimeError(f"No common ancestor for {elem1_tag} {elem2_tag}")
1170        ancestor = ancestor_result.clone
1171        path1 = f'{elem1_tag}[@{elem1_attr}="{elem1_val}"]'
1172        path2 = f'{elem2_tag}[@{elem2_attr}="{elem2_val}"]'
1173        result = ancestor.clone
1174        for child in result.children:
1175            result.delete(child)
1176        result.text = ""
1177        result.tail = ""
1178        target = result
1179        current = ancestor.children[0]
1180
1181        state = 0
1182        while True:
1183            if current is None:
1184                raise RuntimeError(f"No current ancestor for {elem1_tag} {elem2_tag}")
1185            # print 'current', state, current.serialize()
1186            if state == 0:  # before tag 1
1187                if current.xpath(f"descendant-or-self::{path1}"):
1188                    if current.xpath(f"self::{path1}"):
1189                        tail = current.tail
1190                        if tail:
1191                            # got a tail => the parent should be either t:p or t:h
1192                            target.text = tail  # type: ignore
1193                        current, target = current._get_successor(target)  # type: ignore
1194                        state = 1
1195                        continue
1196                    # got T1 in chidren, need further analysis
1197                    new_target = current.clone
1198                    for child in new_target.children:
1199                        new_target.delete(child)
1200                    new_target.text = ""
1201                    new_target.tail = ""
1202                    target.append(new_target)  # type: ignore
1203                    target = new_target
1204                    current = current.children[0]
1205                    continue
1206                else:
1207                    # before tag1 : forget element, go to next one
1208                    current, target = current._get_successor(target)  # type: ignore
1209                    continue
1210            elif state == 1:  # collect elements
1211                further = False
1212                if current.xpath(f"descendant-or-self::{path2}"):
1213                    if current.xpath(f"self::{path2}"):
1214                        # end of trip
1215                        break
1216                    # got T2 in chidren, need further analysis
1217                    further = True
1218                # further analysis needed :
1219                if further:
1220                    new_target = current.clone
1221                    for child in new_target.children:
1222                        new_target.delete(child)
1223                    new_target.text = ""
1224                    new_target.tail = ""
1225                    target.append(new_target)  # type: ignore
1226                    target = new_target
1227                    current = current.children[0]
1228                    continue
1229                # collect
1230                target.append(current.clone)  # type: ignore
1231                current, target = current._get_successor(target)  # type: ignore
1232                continue
1233        # Now resu should be the "parent" of inserted parts
1234        # - a text:h or text:p sigle item (simple case)
1235        # - a upper element, with some text:p, text:h in it => need to be
1236        #   stripped to have a list of text:p, text:h
1237        if result.tag in {"text:p", "text:h"}:
1238            inner = [result]
1239        else:
1240            inner = result.children
1241        return inner
1242
1243    def get_between(
1244        self,
1245        tag1: Element,
1246        tag2: Element,
1247        as_text: bool = False,
1248        clean: bool = True,
1249        no_header: bool = True,
1250    ) -> list | Element | str:
1251        """Returns elements between tag1 and tag2, tag1 and tag2 shall
1252        be unique and having an id attribute.
1253        (WARN: buggy if tag1/tag2 defines a malformed odf xml.)
1254        If as_text is True: returns the text content.
1255        If clean is True: suppress unwanted tags (deletions marks, ...)
1256        If no_header is True: existing text:h are changed in text:p
1257        By default: returns a list of Element, cleaned and without headers.
1258
1259        Implementation and standard retrictions:
1260        Only text:h and text:p sould be 'cut' by an insert tag, so inner parts
1261        of insert tags are:
1262
1263            - any text:h, text:p or sub tag of these
1264
1265            - some text, part of a parent text:h or text:p
1266
1267        Arguments:
1268
1269            tag1 -- Element
1270
1271            tag2 -- Element
1272
1273            as_text -- boolean
1274
1275            clean -- boolean
1276
1277            no_header -- boolean
1278
1279        Return: list of odf_paragraph or odf_header
1280        """
1281        inner = self._get_between_base(tag1, tag2)
1282
1283        if clean:
1284            clean_tags = (
1285                "text:change",
1286                "text:change-start",
1287                "text:change-end",
1288                "text:reference-mark",
1289                "text:reference-mark-start",
1290                "text:reference-mark-end",
1291            )
1292            request_self = " | ".join(["self::%s" % c for c in clean_tags])
1293            inner = [e for e in inner if not e.xpath(request_self)]
1294            request = " | ".join([f"descendant::{tag}" for tag in clean_tags])
1295            for element in inner:
1296                to_del = element.xpath(request)
1297                for elem in to_del:
1298                    if isinstance(elem, Element):
1299                        element.delete(elem)
1300        if no_header:  # crude replace t:h by t:p
1301            new_inner = []
1302            for element in inner:
1303                if element.tag == "text:h":
1304                    children = element.children
1305                    text = element.__element.text
1306                    para = Element.from_tag("text:p")
1307                    para.text = text or ""
1308                    for c in children:
1309                        para.append(c)
1310                    new_inner.append(para)
1311                else:
1312                    new_inner.append(element)
1313            inner = new_inner
1314        if as_text:
1315            return "\n".join([e.get_formatted_text() for e in inner])
1316        else:
1317            return inner
1318
1319    def insert(
1320        self,
1321        element: Element,
1322        xmlposition: int | None = None,
1323        position: int | None = None,
1324        start: bool = False,
1325    ) -> None:
1326        """Insert an element relatively to ourself.
1327
1328        Insert either using DOM vocabulary or by numeric position.
1329        If text start is True, insert the element before any existing text.
1330
1331        Position start at 0.
1332
1333        Arguments:
1334
1335            element -- Element
1336
1337            xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
1338                           or PREV_SIBLING
1339
1340            start -- Boolean
1341
1342            position -- int
1343        """
1344        # child_tag = element.tag
1345        current = self.__element
1346        _element = element.__element
1347        if start:
1348            text = current.text
1349            if text is not None:
1350                current.text = None
1351                tail = _element.tail
1352                if tail is None:
1353                    tail = text
1354                else:
1355                    tail = tail + text
1356                _element.tail = tail
1357            position = 0
1358        if position is not None:
1359            current.insert(position, _element)
1360        elif xmlposition is FIRST_CHILD:
1361            current.insert(0, _element)
1362        elif xmlposition is LAST_CHILD:
1363            current.append(_element)
1364        elif xmlposition is NEXT_SIBLING:
1365            parent = current.getparent()
1366            index = parent.index(current)  # type: ignore
1367            parent.insert(index + 1, _element)  # type: ignore
1368        elif xmlposition is PREV_SIBLING:
1369            parent = current.getparent()
1370            index = parent.index(current)  # type: ignore
1371            parent.insert(index, _element)  # type: ignore
1372        else:
1373            raise ValueError("(xml)position must be defined")
1374
1375    def extend(self, odf_elements: Iterable[Element]) -> None:
1376        """Fast append elements at the end of ourself using extend."""
1377        if odf_elements:
1378            current = self.__element
1379            elements = [element.__element for element in odf_elements]
1380            current.extend(elements)
1381
1382    def append(self, str_or_element: str | Element) -> None:
1383        """Insert element or text in the last position."""
1384        current = self.__element
1385        if isinstance(str_or_element, str):
1386            # Has children ?
1387            children = list(current.iterchildren())
1388            if children:
1389                # Append to tail of the last child
1390                last_child = children[-1]
1391                text = last_child.tail
1392                text = text if text is not None else ""
1393                text += str_or_element
1394                last_child.tail = text
1395            else:
1396                # Append to text of the element
1397                text = current.text
1398                text = text if text is not None else ""
1399                text += str_or_element
1400                current.text = text
1401        elif isinstance(str_or_element, Element):
1402            current.append(str_or_element.__element)
1403        else:
1404            raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')
1405
1406    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
1407        """Delete the given element from the XML tree. If no element is given,
1408        "self" is deleted. The XML library may allow to continue to use an
1409        element now "orphan" as long as you have a reference to it.
1410
1411        if keep_tail is True (default), the tail text is not erased.
1412
1413        Arguments:
1414
1415            child -- Element
1416
1417            keep_tail -- boolean (default to True), True for most usages.
1418        """
1419        if child is None:
1420            parent = self.parent
1421            if parent is None:
1422                raise ValueError(f"Can't delete the root element\n{self.serialize()}")
1423            child = self
1424        else:
1425            parent = self
1426        if keep_tail and child.__element.tail is not None:
1427            current = child.__element
1428            tail = str(current.tail)
1429            current.tail = None
1430            prev = current.getprevious()
1431            if prev is not None:
1432                if prev.tail is None:
1433                    prev.tail = tail
1434                else:
1435                    prev.tail += tail
1436            else:
1437                if parent.__element.text is None:
1438                    parent.__element.text = tail
1439                else:
1440                    parent.__element.text += tail
1441        parent.__element.remove(child.__element)
1442
1443    def replace_element(self, old_element: Element, new_element: Element) -> None:
1444        """Replaces in place a sub element with the element passed as second
1445        argument.
1446
1447        Warning : no clone for old element.
1448        """
1449        current = self.__element
1450        current.replace(old_element.__element, new_element.__element)
1451
1452    def strip_elements(
1453        self,
1454        sub_elements: Element | Iterable[Element],
1455    ) -> Element | list:
1456        """Remove the tags of provided elements, keeping inner childs and text.
1457
1458        Return : the striped element.
1459
1460        Warning : no clone in sub_elements list.
1461
1462        Arguments:
1463
1464            sub_elements -- Element or list of Element
1465        """
1466        if not sub_elements:
1467            return self
1468        if isinstance(sub_elements, Element):
1469            sub_elements = (sub_elements,)
1470        replacer = _get_lxml_tag("text:this-will-be-removed")
1471        for element in sub_elements:
1472            element.__element.tag = replacer
1473        strip = ("text:this-will-be-removed",)
1474        return self.strip_tags(strip=strip, default=None)
1475
1476    def strip_tags(
1477        self,
1478        strip: Iterable[str] | None = None,
1479        protect: Iterable[str] | None = None,
1480        default: str | None = "text:p",
1481    ) -> Element | list:
1482        """Remove the tags listed in strip, recursively, keeping inner childs
1483        and text. Tags listed in protect stop the removal one level depth. If
1484        the first level element is stripped, default is used to embed the
1485        content in the default element. If default is None and first level is
1486        striped, a list of text and children is returned. Return : the striped
1487        element.
1488
1489        strip_tags should be used by on purpose methods (strip_span ...)
1490        (Method name taken from lxml).
1491
1492        Arguments:
1493
1494            strip -- iterable list of str odf tags, or None
1495
1496            protect -- iterable list of str odf tags, or None
1497
1498            default -- str odf tag, or None
1499
1500        Return:
1501
1502            Element.
1503        """
1504        if not strip:
1505            return self
1506        if not protect:
1507            protect = ()
1508        protected = False
1509        element, modified = Element._strip_tags(self, strip, protect, protected)
1510        if modified and isinstance(element, list) and default:
1511            new = Element.from_tag(default)
1512            for content in element:
1513                if isinstance(content, Element):
1514                    new.append(content)
1515                else:
1516                    new.text = content
1517            element = new
1518        return element
1519
1520    @staticmethod
1521    def _strip_tags(  # noqa:C901
1522        element: Element,
1523        strip: Iterable[str],
1524        protect: Iterable[str],
1525        protected: bool,
1526    ) -> tuple[Element | list, bool]:
1527        """Sub method for strip_tags()."""
1528        element_clone = element.clone
1529        modified = False
1530        children = []
1531        if protect and element.tag in protect:
1532            protect_below = True
1533        else:
1534            protect_below = False
1535        for child in element_clone.children:
1536            striped_child, is_modified = Element._strip_tags(
1537                child, strip, protect, protect_below
1538            )
1539            if is_modified:
1540                modified = True
1541            if isinstance(striped_child, list):
1542                children.extend(striped_child)
1543            else:
1544                children.append(striped_child)
1545
1546        text = element_clone.text
1547        tail = element_clone.tail
1548        if not protected and strip and element.tag in strip:
1549            element_result: list[Element | str] = []
1550            if text is not None:
1551                element_result.append(text)
1552            for child in children:
1553                element_result.append(child)
1554            if tail is not None:
1555                element_result.append(tail)
1556            return (element_result, True)
1557        else:
1558            if not modified:
1559                return (element, False)
1560            element.clear()
1561            try:
1562                for key, value in element_clone.attributes.items():
1563                    element.set_attribute(key, value)
1564            except ValueError:
1565                sys.stderr.write(f"strip_tags(): bad attribute in {element_clone}\n")
1566            if text is not None:
1567                element.append(text)
1568            for child in children:
1569                element.append(child)
1570            if tail is not None:
1571                element.tail = tail
1572            return (element, True)
1573
1574    def xpath(self, xpath_query: str) -> list[Element | Text]:
1575        """Apply XPath query to the element and its subtree. Return list of
1576        Element or Text instances translated from the nodes found.
1577        """
1578        element = self.__element
1579        xpath_instance = xpath_compile(xpath_query)
1580        elements = xpath_instance(element)
1581        result: list[Element | Text] = []
1582        if hasattr(elements, "__iter__"):
1583            for obj in elements:  # type: ignore
1584                if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)):
1585                    result.append(Text(obj))
1586                elif isinstance(obj, _Element):
1587                    result.append(Element.from_tag(obj))
1588                # else:
1589                #     result.append(obj)
1590        return result
1591
1592    def clear(self) -> None:
1593        """Remove text, children and attributes from the element."""
1594        self.__element.clear()
1595        if hasattr(self, "_tmap"):
1596            self._tmap: list[int] = []
1597        if hasattr(self, "_cmap"):
1598            self._cmap: list[int] = []
1599        if hasattr(self, "_rmap"):
1600            self._rmap: list[int] = []
1601        if hasattr(self, "_indexes"):
1602            remember = False
1603            if "_rmap" in self._indexes:
1604                remember = True
1605            self._indexes: dict[str, dict] = {}
1606            self._indexes["_cmap"] = {}
1607            self._indexes["_tmap"] = {}
1608            if remember:
1609                self._indexes["_rmap"] = {}
1610
1611    @property
1612    def clone(self) -> Element:
1613        clone = deepcopy(self.__element)
1614        root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES)
1615        root.append(clone)
1616        return self.from_tag(clone)
1617
1618        # slow data = tostring(self.__element, encoding='unicode')
1619        # return self.from_tag(data)
1620
1621    @staticmethod
1622    def _strip_namespaces(data: str) -> str:
1623        """Remove xmlns:* fields from serialized XML."""
1624        return re.sub(r' xmlns:\w*="[\w:\-\/\.#]*"', "", data)
1625
1626    def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1627        """Return text serialization of XML element."""
1628        # This copy bypasses serialization side-effects in lxml
1629        native = deepcopy(self.__element)
1630        data = tostring(
1631            native, with_tail=False, pretty_print=pretty, encoding="unicode"
1632        )
1633        if with_ns:
1634            return data
1635        # Remove namespaces
1636        return self._strip_namespaces(data)
1637
1638    # Element helpers usable from any context
1639
1640    @property
1641    def document_body(self) -> Element | None:
1642        """Return the document body : 'office:body'"""
1643        return self.get_element("//office:body/*[1]")
1644
1645    @document_body.setter
1646    def document_body(self, new_body: Element) -> None:
1647        """Change in place the full document body content."""
1648        body = self.document_body
1649        if body is None:
1650            raise ValueError("//office:body not found in document")
1651        tail = body.tail
1652        body.clear()
1653        for item in new_body.children:
1654            body.append(item)
1655        if tail:
1656            body.tail = tail
1657
1658    def get_formatted_text(self, context: dict | None = None) -> str:
1659        """This function should return a beautiful version of the text."""
1660        return ""
1661
1662    def get_styled_elements(self, name: str = "") -> list[Element]:
1663        """Brute-force to find paragraphs, tables, etc. using the given style
1664        name (or all by default).
1665
1666        Arguments:
1667
1668            name -- str
1669
1670        Return: list
1671        """
1672        # FIXME incomplete (and possibly inaccurate)
1673        return (
1674            self._filtered_elements("descendant::*", text_style=name)
1675            + self._filtered_elements("descendant::*", draw_style=name)
1676            + self._filtered_elements("descendant::*", draw_text_style=name)
1677            + self._filtered_elements("descendant::*", table_style=name)
1678            + self._filtered_elements("descendant::*", page_layout=name)
1679            + self._filtered_elements("descendant::*", master_page=name)
1680            + self._filtered_elements("descendant::*", parent_style=name)
1681        )
1682
1683    # Common attributes
1684
1685    def _get_inner_text(self, tag: str) -> str | None:
1686        element = self.get_element(tag)
1687        if element is None:
1688            return None
1689        return element.text
1690
1691    def _set_inner_text(self, tag: str, text: str) -> None:
1692        element = self.get_element(tag)
1693        if element is None:
1694            element = Element.from_tag(tag)
1695            self.append(element)
1696        element.text = text
1697
1698    # Dublin core
1699
1700    @property
1701    def dc_creator(self) -> str | None:
1702        """Get dc:creator value.
1703
1704        Return: str (or None if inexistant)
1705        """
1706        return self._get_inner_text("dc:creator")
1707
1708    @dc_creator.setter
1709    def dc_creator(self, creator: str) -> None:
1710        """Set dc:creator value.
1711
1712        Arguments:
1713
1714            creator -- str
1715        """
1716        self._set_inner_text("dc:creator", creator)
1717
1718    @property
1719    def dc_date(self) -> datetime | None:
1720        """Get the dc:date value.
1721
1722        Return: datetime (or None if inexistant)
1723        """
1724        date = self._get_inner_text("dc:date")
1725        if date is None:
1726            return None
1727        return DateTime.decode(date)
1728
1729    @dc_date.setter
1730    def dc_date(self, date: datetime) -> None:
1731        """Set the dc:date value.
1732
1733        Arguments:
1734
1735            darz -- datetime
1736        """
1737        self._set_inner_text("dc:date", DateTime.encode(date))
1738
1739    # SVG
1740
1741    @property
1742    def svg_title(self) -> str | None:
1743        return self._get_inner_text("svg:title")
1744
1745    @svg_title.setter
1746    def svg_title(self, title: str) -> None:
1747        self._set_inner_text("svg:title", title)
1748
1749    @property
1750    def svg_description(self) -> str | None:
1751        return self._get_inner_text("svg:desc")
1752
1753    @svg_description.setter
1754    def svg_description(self, description: str) -> None:
1755        self._set_inner_text("svg:desc", description)
1756
1757    # Sections
1758
1759    def get_sections(
1760        self,
1761        style: str | None = None,
1762        content: str | None = None,
1763    ) -> list[Element]:
1764        """Return all the sections that match the criteria.
1765
1766        Arguments:
1767
1768            style -- str
1769
1770            content -- str regex
1771
1772        Return: list of Element
1773        """
1774        return self._filtered_elements(
1775            "text:section", text_style=style, content=content
1776        )
1777
1778    def get_section(
1779        self,
1780        position: int = 0,
1781        content: str | None = None,
1782    ) -> Element | None:
1783        """Return the section that matches the criteria.
1784
1785        Arguments:
1786
1787            position -- int
1788
1789            content -- str regex
1790
1791        Return: Element or None if not found
1792        """
1793        return self._filtered_element(
1794            "descendant::text:section", position, content=content
1795        )
1796
1797    # Paragraphs
1798
1799    def get_paragraphs(
1800        self,
1801        style: str | None = None,
1802        content: str | None = None,
1803    ) -> list[Element]:
1804        """Return all the paragraphs that match the criteria.
1805
1806        Arguments:
1807
1808            style -- str
1809
1810            content -- str regex
1811
1812        Return: list of Paragraph
1813        """
1814        return self._filtered_elements(
1815            "descendant::text:p", text_style=style, content=content
1816        )
1817
1818    def get_paragraph(
1819        self,
1820        position: int = 0,
1821        content: str | None = None,
1822    ) -> Element | None:
1823        """Return the paragraph that matches the criteria.
1824
1825        Arguments:
1826
1827            position -- int
1828
1829            content -- str regex
1830
1831        Return: Paragraph or None if not found
1832        """
1833        return self._filtered_element("descendant::text:p", position, content=content)
1834
1835    # Span
1836
1837    def get_spans(
1838        self,
1839        style: str | None = None,
1840        content: str | None = None,
1841    ) -> list[Element]:
1842        """Return all the spans that match the criteria.
1843
1844        Arguments:
1845
1846            style -- str
1847
1848            content -- str regex
1849
1850        Return: list of Span
1851        """
1852        return self._filtered_elements(
1853            "descendant::text:span", text_style=style, content=content
1854        )
1855
1856    def get_span(
1857        self,
1858        position: int = 0,
1859        content: str | None = None,
1860    ) -> Element | None:
1861        """Return the span that matches the criteria.
1862
1863        Arguments:
1864
1865            position -- int
1866
1867            content -- str regex
1868
1869        Return: Span or None if not found
1870        """
1871        return self._filtered_element(
1872            "descendant::text:span", position, content=content
1873        )
1874
1875    # Headers
1876
1877    def get_headers(
1878        self,
1879        style: str | None = None,
1880        outline_level: str | None = None,
1881        content: str | None = None,
1882    ) -> list[Element]:
1883        """Return all the Headers that match the criteria.
1884
1885        Arguments:
1886
1887            style -- str
1888
1889            content -- str regex
1890
1891        Return: list of Header
1892        """
1893        return self._filtered_elements(
1894            "descendant::text:h",
1895            text_style=style,
1896            outline_level=outline_level,
1897            content=content,
1898        )
1899
1900    def get_header(
1901        self,
1902        position: int = 0,
1903        outline_level: str | None = None,
1904        content: str | None = None,
1905    ) -> Element | None:
1906        """Return the Header that matches the criteria.
1907
1908        Arguments:
1909
1910            position -- int
1911
1912            content -- str regex
1913
1914        Return: Header or None if not found
1915        """
1916        return self._filtered_element(
1917            "descendant::text:h",
1918            position,
1919            outline_level=outline_level,
1920            content=content,
1921        )
1922
1923    # Lists
1924
1925    def get_lists(
1926        self,
1927        style: str | None = None,
1928        content: str | None = None,
1929    ) -> list[Element]:
1930        """Return all the lists that match the criteria.
1931
1932        Arguments:
1933
1934            style -- str
1935
1936            content -- str regex
1937
1938        Return: list of List
1939        """
1940        return self._filtered_elements(
1941            "descendant::text:list", text_style=style, content=content
1942        )
1943
1944    def get_list(
1945        self,
1946        position: int = 0,
1947        content: str | None = None,
1948    ) -> Element | None:
1949        """Return the list that matches the criteria.
1950
1951        Arguments:
1952
1953            position -- int
1954
1955            content -- str regex
1956
1957        Return: List or None if not found
1958        """
1959        return self._filtered_element(
1960            "descendant::text:list", position, content=content
1961        )
1962
1963    # Frames
1964
1965    def get_frames(
1966        self,
1967        presentation_class: str | None = None,
1968        style: str | None = None,
1969        title: str | None = None,
1970        description: str | None = None,
1971        content: str | None = None,
1972    ) -> list[Element]:
1973        """Return all the frames that match the criteria.
1974
1975        Arguments:
1976
1977            presentation_class -- str
1978
1979            style -- str
1980
1981            title -- str regex
1982
1983            description -- str regex
1984
1985            content -- str regex
1986
1987        Return: list of Frame
1988        """
1989        return self._filtered_elements(
1990            "descendant::draw:frame",
1991            presentation_class=presentation_class,
1992            draw_style=style,
1993            svg_title=title,
1994            svg_desc=description,
1995            content=content,
1996        )
1997
1998    def get_frame(
1999        self,
2000        position: int = 0,
2001        name: str | None = None,
2002        presentation_class: str | None = None,
2003        title: str | None = None,
2004        description: str | None = None,
2005        content: str | None = None,
2006    ) -> Element | None:
2007        """Return the section that matches the criteria.
2008
2009        Arguments:
2010
2011            position -- int
2012
2013            name -- str
2014
2015            presentation_class -- str
2016
2017            title -- str regex
2018
2019            description -- str regex
2020
2021            content -- str regex
2022
2023        Return: Frame or None if not found
2024        """
2025        return self._filtered_element(
2026            "descendant::draw:frame",
2027            position,
2028            draw_name=name,
2029            presentation_class=presentation_class,
2030            svg_title=title,
2031            svg_desc=description,
2032            content=content,
2033        )
2034
2035    # Images
2036
2037    def get_images(
2038        self,
2039        style: str | None = None,
2040        url: str | None = None,
2041        content: str | None = None,
2042    ) -> list[Element]:
2043        """Return all the images matching the criteria.
2044
2045        Arguments:
2046
2047            style -- str
2048
2049            url -- str regex
2050
2051            content -- str regex
2052
2053        Return: list of Element
2054        """
2055        return self._filtered_elements(
2056            "descendant::draw:image", text_style=style, url=url, content=content
2057        )
2058
2059    def get_image(
2060        self,
2061        position: int = 0,
2062        name: str | None = None,
2063        url: str | None = None,
2064        content: str | None = None,
2065    ) -> Element | None:
2066        """Return the image matching the criteria.
2067
2068        Arguments:
2069
2070            position -- int
2071
2072            name -- str
2073
2074            url -- str regex
2075
2076            content -- str regex
2077
2078        Return: Element or None if not found
2079        """
2080        # The frame is holding the name
2081        if name is not None:
2082            frame = self._filtered_element(
2083                "descendant::draw:frame", position, draw_name=name
2084            )
2085            if frame is None:
2086                return None
2087            # The name is supposedly unique
2088            return frame.get_element("draw:image")
2089        return self._filtered_element(
2090            "descendant::draw:image", position, url=url, content=content
2091        )
2092
2093    # Tables
2094
2095    def get_tables(
2096        self,
2097        style: str | None = None,
2098        content: str | None = None,
2099    ) -> list[Element]:
2100        """Return all the tables that match the criteria.
2101
2102        Arguments:
2103
2104            style -- str
2105
2106            content -- str regex
2107
2108        Return: list of Table
2109        """
2110        return self._filtered_elements(
2111            "descendant::table:table", table_style=style, content=content
2112        )
2113
2114    def get_table(
2115        self,
2116        position: int = 0,
2117        name: str | None = None,
2118        content: str | None = None,
2119    ) -> Element | None:
2120        """Return the table that matches the criteria.
2121
2122        Arguments:
2123
2124            position -- int
2125
2126            name -- str
2127
2128            content -- str regex
2129
2130        Return: Table or None if not found
2131        """
2132        if name is None and content is None:
2133            result = self._filtered_element("descendant::table:table", position)
2134        else:
2135            result = self._filtered_element(
2136                "descendant::table:table",
2137                position,
2138                table_name=name,
2139                content=content,
2140            )
2141        return result
2142
2143    # Named Range
2144
2145    def get_named_ranges(self) -> list[Element]:
2146        """Return all the tables named ranges.
2147
2148        Return: list of odf_named_range
2149        """
2150        named_ranges = self.get_elements(
2151            "descendant::table:named-expressions/table:named-range"
2152        )
2153        return named_ranges
2154
2155    def get_named_range(self, name: str) -> Element | None:
2156        """Return the named range of specified name, or None if not found.
2157
2158        Arguments:
2159
2160            name -- str
2161
2162        Return: NamedRange
2163        """
2164        named_range = self.get_elements(
2165            f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]'
2166        )
2167        if named_range:
2168            return named_range[0]
2169        else:
2170            return None
2171
2172    def append_named_range(self, named_range: Element) -> None:
2173        """Append the named range to the spreadsheet, replacing existing named
2174        range of same name if any.
2175
2176        Arguments:
2177
2178            named_range --  NamedRange
2179        """
2180        if self.tag != "office:spreadsheet":
2181            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2182        named_expressions = self.get_element("table:named-expressions")
2183        if not named_expressions:
2184            named_expressions = Element.from_tag("table:named-expressions")
2185            self.append(named_expressions)
2186        # exists ?
2187        current = named_expressions.get_element(
2188            f'table:named-range[@table:name="{named_range.name}"][1]'  # type:ignore
2189        )
2190        if current:
2191            named_expressions.delete(current)
2192        named_expressions.append(named_range)
2193
2194    def delete_named_range(self, name: str) -> None:
2195        """Delete the Named Range of specified name from the spreadsheet.
2196
2197        Arguments:
2198
2199            name -- str
2200        """
2201        if self.tag != "office:spreadsheet":
2202            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2203        named_range = self.get_named_range(name)
2204        if not named_range:
2205            return
2206        named_range.delete()
2207        named_expressions = self.get_element("table:named-expressions")
2208        if not named_expressions:
2209            return
2210        element = named_expressions.__element
2211        children = list(element.iterchildren())
2212        if not children:
2213            self.delete(named_expressions)
2214
2215    # Notes
2216
2217    def get_notes(
2218        self,
2219        note_class: str | None = None,
2220        content: str | None = None,
2221    ) -> list[Element]:
2222        """Return all the notes that match the criteria.
2223
2224        Arguments:
2225
2226            note_class -- 'footnote' or 'endnote'
2227
2228            content -- str regex
2229
2230        Return: list of Note
2231        """
2232        return self._filtered_elements(
2233            "descendant::text:note", note_class=note_class, content=content
2234        )
2235
2236    def get_note(
2237        self,
2238        position: int = 0,
2239        note_id: str | None = None,
2240        note_class: str | None = None,
2241        content: str | None = None,
2242    ) -> Element | None:
2243        """Return the note that matches the criteria.
2244
2245        Arguments:
2246
2247            position -- int
2248
2249            note_id -- str
2250
2251            note_class -- 'footnote' or 'endnote'
2252
2253            content -- str regex
2254
2255        Return: Note or None if not found
2256        """
2257        return self._filtered_element(
2258            "descendant::text:note",
2259            position,
2260            text_id=note_id,
2261            note_class=note_class,
2262            content=content,
2263        )
2264
2265    # Annotations
2266
2267    def get_annotations(
2268        self,
2269        creator: str | None = None,
2270        start_date: datetime | None = None,
2271        end_date: datetime | None = None,
2272        content: str | None = None,
2273    ) -> list[Element]:
2274        """Return all the annotations that match the criteria.
2275
2276        Arguments:
2277
2278            creator -- str
2279
2280            start_date -- datetime instance
2281
2282            end_date --  datetime instance
2283
2284            content -- str regex
2285
2286        Return: list of Annotation
2287        """
2288        annotations = []
2289        for annotation in self._filtered_elements(
2290            "descendant::office:annotation", content=content
2291        ):
2292            if creator is not None and creator != annotation.dc_creator:
2293                continue
2294            date = annotation.dc_date
2295            if date is None:
2296                continue
2297            if start_date is not None and date < start_date:
2298                continue
2299            if end_date is not None and date >= end_date:
2300                continue
2301            annotations.append(annotation)
2302        return annotations
2303
2304    def get_annotation(
2305        self,
2306        position: int = 0,
2307        creator: str | None = None,
2308        start_date: datetime | None = None,
2309        end_date: datetime | None = None,
2310        content: str | None = None,
2311        name: str | None = None,
2312    ) -> Element | None:
2313        """Return the annotation that matches the criteria.
2314
2315        Arguments:
2316
2317            position -- int
2318
2319            creator -- str
2320
2321            start_date -- datetime instance
2322
2323            end_date -- datetime instance
2324
2325            content -- str regex
2326
2327            name -- str
2328
2329        Return: Annotation or None if not found
2330        """
2331        if name is not None:
2332            return self._filtered_element(
2333                "descendant::office:annotation", 0, office_name=name
2334            )
2335        annotations = self.get_annotations(
2336            creator=creator, start_date=start_date, end_date=end_date, content=content
2337        )
2338        if not annotations:
2339            return None
2340        try:
2341            return annotations[position]
2342        except IndexError:
2343            return None
2344
2345    def get_annotation_ends(self) -> list[Element]:
2346        """Return all the annotation ends.
2347
2348        Return: list of Element
2349        """
2350        return self._filtered_elements("descendant::office:annotation-end")
2351
2352    def get_annotation_end(
2353        self,
2354        position: int = 0,
2355        name: str | None = None,
2356    ) -> Element | None:
2357        """Return the annotation end that matches the criteria.
2358
2359        Arguments:
2360
2361            position -- int
2362
2363            name -- str
2364
2365        Return: Element or None if not found
2366        """
2367        return self._filtered_element(
2368            "descendant::office:annotation-end", position, office_name=name
2369        )
2370
2371    # office:names
2372
2373    def get_office_names(self) -> list[str]:
2374        """Return all the used office:name tags values of the element.
2375
2376        Return: list of unique str
2377        """
2378        name_xpath_query = xpath_compile("//@office:name")
2379        response = name_xpath_query(self.__element)
2380        if not isinstance(response, list):
2381            return []
2382        return list({str(name) for name in response if name})
2383
2384    # Variables
2385
2386    def get_variable_decls(self) -> Element:
2387        """Return the container for variable declarations. Created if not
2388        found.
2389
2390        Return: Element
2391        """
2392        variable_decls = self.get_element("//text:variable-decls")
2393        if variable_decls is None:
2394            body = self.document_body
2395            if not body:
2396                raise ValueError("Empty document.body")
2397            body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD)
2398            variable_decls = body.get_element("//text:variable-decls")
2399
2400        return variable_decls  # type:ignore
2401
2402    def get_variable_decl_list(self) -> list[Element]:
2403        """Return all the variable declarations.
2404
2405        Return: list of Element
2406        """
2407        return self._filtered_elements("descendant::text:variable-decl")
2408
2409    def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2410        """return the variable declaration for the given name.
2411
2412        Arguments:
2413
2414            name -- str
2415
2416            position -- int
2417
2418        return: Element or none if not found
2419        """
2420        return self._filtered_element(
2421            "descendant::text:variable-decl", position, text_name=name
2422        )
2423
2424    def get_variable_sets(self, name: str | None = None) -> list[Element]:
2425        """Return all the variable sets that match the criteria.
2426
2427        Arguments:
2428
2429            name -- str
2430
2431        Return: list of Element
2432        """
2433        return self._filtered_elements("descendant::text:variable-set", text_name=name)
2434
2435    def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2436        """Return the variable set for the given name (last one by default).
2437
2438        Arguments:
2439
2440            name -- str
2441
2442            position -- int
2443
2444        Return: Element or None if not found
2445        """
2446        return self._filtered_element(
2447            "descendant::text:variable-set", position, text_name=name
2448        )
2449
2450    def get_variable_set_value(
2451        self,
2452        name: str,
2453        value_type: str | None = None,
2454    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2455        """Return the last value of the given variable name.
2456
2457        Arguments:
2458
2459            name -- str
2460
2461            value_type -- 'boolean', 'currency', 'date', 'float',
2462                          'percentage', 'string', 'time' or automatic
2463
2464        Return: most appropriate Python type
2465        """
2466        variable_set = self.get_variable_set(name)
2467        if not variable_set:
2468            return None
2469        return variable_set.get_value(value_type)  # type: ignore
2470
2471    # User fields
2472
2473    def get_user_field_decls(self) -> Element | None:
2474        """Return the container for user field declarations. Created if not
2475        found.
2476
2477        Return: Element
2478        """
2479        user_field_decls = self.get_element("//text:user-field-decls")
2480        if user_field_decls is None:
2481            body = self.document_body
2482            if not body:
2483                raise ValueError("Empty document.body")
2484            body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD)
2485            user_field_decls = body.get_element("//text:user-field-decls")
2486
2487        return user_field_decls
2488
2489    def get_user_field_decl_list(self) -> list[Element]:
2490        """Return all the user field declarations.
2491
2492        Return: list of Element
2493        """
2494        return self._filtered_elements("descendant::text:user-field-decl")
2495
2496    def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2497        """return the user field declaration for the given name.
2498
2499        return: Element or none if not found
2500        """
2501        return self._filtered_element(
2502            "descendant::text:user-field-decl", position, text_name=name
2503        )
2504
2505    def get_user_field_value(
2506        self, name: str, value_type: str | None = None
2507    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2508        """Return the value of the given user field name.
2509
2510        Arguments:
2511
2512            name -- str
2513
2514            value_type -- 'boolean', 'currency', 'date', 'float',
2515                          'percentage', 'string', 'time' or automatic
2516
2517        Return: most appropriate Python type
2518        """
2519        user_field_decl = self.get_user_field_decl(name)
2520        if user_field_decl is None:
2521            return None
2522        return user_field_decl.get_value(value_type)  # type: ignore
2523
2524    # User defined fields
2525    # They are fields who should contain a copy of a user defined medtadata
2526
2527    def get_user_defined_list(self) -> list[Element]:
2528        """Return all the user defined field declarations.
2529
2530        Return: list of Element
2531        """
2532        return self._filtered_elements("descendant::text:user-defined")
2533
2534    def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2535        """return the user defined declaration for the given name.
2536
2537        return: Element or none if not found
2538        """
2539        return self._filtered_element(
2540            "descendant::text:user-defined", position, text_name=name
2541        )
2542
2543    def get_user_defined_value(
2544        self, name: str, value_type: str | None = None
2545    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2546        """Return the value of the given user defined field name.
2547
2548        Arguments:
2549
2550            name -- str
2551
2552            value_type -- 'boolean', 'date', 'float',
2553                          'string', 'time' or automatic
2554
2555        Return: most appropriate Python type
2556        """
2557        user_defined = self.get_user_defined(name)
2558        if user_defined is None:
2559            return None
2560        return user_defined.get_value(value_type)  # type: ignore
2561
2562    # Draw Pages
2563
2564    def get_draw_pages(
2565        self,
2566        style: str | None = None,
2567        content: str | None = None,
2568    ) -> list[Element]:
2569        """Return all the draw pages that match the criteria.
2570
2571        Arguments:
2572
2573            style -- str
2574
2575            content -- str regex
2576
2577        Return: list of DrawPage
2578        """
2579        return self._filtered_elements(
2580            "descendant::draw:page", draw_style=style, content=content
2581        )
2582
2583    def get_draw_page(
2584        self,
2585        position: int = 0,
2586        name: str | None = None,
2587        content: str | None = None,
2588    ) -> Element | None:
2589        """Return the draw page that matches the criteria.
2590
2591        Arguments:
2592
2593            position -- int
2594
2595            name -- str
2596
2597            content -- str regex
2598
2599        Return: DrawPage or None if not found
2600        """
2601        return self._filtered_element(
2602            "descendant::draw:page", position, draw_name=name, content=content
2603        )
2604
2605    # Links
2606
2607    def get_links(
2608        self,
2609        name: str | None = None,
2610        title: str | None = None,
2611        url: str | None = None,
2612        content: str | None = None,
2613    ) -> list[Element]:
2614        """Return all the links that match the criteria.
2615
2616        Arguments:
2617
2618            name -- str
2619
2620            title -- str
2621
2622            url -- str regex
2623
2624            content -- str regex
2625
2626        Return: list of Element
2627        """
2628        return self._filtered_elements(
2629            "descendant::text:a",
2630            office_name=name,
2631            office_title=title,
2632            url=url,
2633            content=content,
2634        )
2635
2636    def get_link(
2637        self,
2638        position: int = 0,
2639        name: str | None = None,
2640        title: str | None = None,
2641        url: str | None = None,
2642        content: str | None = None,
2643    ) -> Element | None:
2644        """Return the link that matches the criteria.
2645
2646        Arguments:
2647
2648            position -- int
2649
2650            name -- str
2651
2652            title -- str
2653
2654            url -- str regex
2655
2656            content -- str regex
2657
2658        Return: Element or None if not found
2659        """
2660        return self._filtered_element(
2661            "descendant::text:a",
2662            position,
2663            office_name=name,
2664            office_title=title,
2665            url=url,
2666            content=content,
2667        )
2668
2669    # Bookmarks
2670
2671    def get_bookmarks(self) -> list[Element]:
2672        """Return all the bookmarks.
2673
2674        Return: list of Element
2675        """
2676        return self._filtered_elements("descendant::text:bookmark")
2677
2678    def get_bookmark(
2679        self,
2680        position: int = 0,
2681        name: str | None = None,
2682    ) -> Element | None:
2683        """Return the bookmark that matches the criteria.
2684
2685        Arguments:
2686
2687            position -- int
2688
2689            name -- str
2690
2691        Return: Bookmark or None if not found
2692        """
2693        return self._filtered_element(
2694            "descendant::text:bookmark", position, text_name=name
2695        )
2696
2697    def get_bookmark_starts(self) -> list[Element]:
2698        """Return all the bookmark starts.
2699
2700        Return: list of Element
2701        """
2702        return self._filtered_elements("descendant::text:bookmark-start")
2703
2704    def get_bookmark_start(
2705        self,
2706        position: int = 0,
2707        name: str | None = None,
2708    ) -> Element | None:
2709        """Return the bookmark start that matches the criteria.
2710
2711        Arguments:
2712
2713            position -- int
2714
2715            name -- str
2716
2717        Return: Element or None if not found
2718        """
2719        return self._filtered_element(
2720            "descendant::text:bookmark-start", position, text_name=name
2721        )
2722
2723    def get_bookmark_ends(self) -> list[Element]:
2724        """Return all the bookmark ends.
2725
2726        Return: list of Element
2727        """
2728        return self._filtered_elements("descendant::text:bookmark-end")
2729
2730    def get_bookmark_end(
2731        self,
2732        position: int = 0,
2733        name: str | None = None,
2734    ) -> Element | None:
2735        """Return the bookmark end that matches the criteria.
2736
2737        Arguments:
2738
2739            position -- int
2740
2741            name -- str
2742
2743        Return: Element or None if not found
2744        """
2745        return self._filtered_element(
2746            "descendant::text:bookmark-end", position, text_name=name
2747        )
2748
2749    # Reference marks
2750
2751    def get_reference_marks_single(self) -> list[Element]:
2752        """Return all the reference marks. Search only the tags
2753        text:reference-mark.
2754        Consider using : get_reference_marks()
2755
2756        Return: list of Element
2757        """
2758        return self._filtered_elements("descendant::text:reference-mark")
2759
2760    def get_reference_mark_single(
2761        self,
2762        position: int = 0,
2763        name: str | None = None,
2764    ) -> Element | None:
2765        """Return the reference mark that matches the criteria. Search only the
2766        tags text:reference-mark.
2767        Consider using : get_reference_mark()
2768
2769        Arguments:
2770
2771            position -- int
2772
2773            name -- str
2774
2775        Return: Element or None if not found
2776        """
2777        return self._filtered_element(
2778            "descendant::text:reference-mark", position, text_name=name
2779        )
2780
2781    def get_reference_mark_starts(self) -> list[Element]:
2782        """Return all the reference mark starts. Search only the tags
2783        text:reference-mark-start.
2784        Consider using : get_reference_marks()
2785
2786        Return: list of Element
2787        """
2788        return self._filtered_elements("descendant::text:reference-mark-start")
2789
2790    def get_reference_mark_start(
2791        self,
2792        position: int = 0,
2793        name: str | None = None,
2794    ) -> Element | None:
2795        """Return the reference mark start that matches the criteria. Search
2796        only the tags text:reference-mark-start.
2797        Consider using : get_reference_mark()
2798
2799        Arguments:
2800
2801            position -- int
2802
2803            name -- str
2804
2805        Return: Element or None if not found
2806        """
2807        return self._filtered_element(
2808            "descendant::text:reference-mark-start", position, text_name=name
2809        )
2810
2811    def get_reference_mark_ends(self) -> list[Element]:
2812        """Return all the reference mark ends. Search only the tags
2813        text:reference-mark-end.
2814        Consider using : get_reference_marks()
2815
2816        Return: list of Element
2817        """
2818        return self._filtered_elements("descendant::text:reference-mark-end")
2819
2820    def get_reference_mark_end(
2821        self,
2822        position: int = 0,
2823        name: str | None = None,
2824    ) -> Element | None:
2825        """Return the reference mark end that matches the criteria. Search only
2826        the tags text:reference-mark-end.
2827        Consider using : get_reference_marks()
2828
2829        Arguments:
2830
2831            position -- int
2832
2833            name -- str
2834
2835        Return: Element or None if not found
2836        """
2837        return self._filtered_element(
2838            "descendant::text:reference-mark-end", position, text_name=name
2839        )
2840
2841    def get_reference_marks(self) -> list[Element]:
2842        """Return all the reference marks, either single position reference
2843        (text:reference-mark) or start of range reference
2844        (text:reference-mark-start).
2845
2846        Return: list of Element
2847        """
2848        return self._filtered_elements(
2849            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2850        )
2851
2852    def get_reference_mark(
2853        self,
2854        position: int = 0,
2855        name: str | None = None,
2856    ) -> Element | None:
2857        """Return the reference mark that match the criteria. Either single
2858        position reference mark (text:reference-mark) or start of range
2859        reference (text:reference-mark-start).
2860
2861        Arguments:
2862
2863            position -- int
2864
2865            name -- str
2866
2867        Return: Element or None if not found
2868        """
2869        if name:
2870            request = (
2871                f"descendant::text:reference-mark-start"
2872                f'[@text:name="{name}"] '
2873                f"| descendant::text:reference-mark"
2874                f'[@text:name="{name}"]'
2875            )
2876            return self._filtered_element(request, position=0)
2877        request = (
2878            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2879        )
2880        return self._filtered_element(request, position)
2881
2882    def get_references(self, name: str | None = None) -> list[Element]:
2883        """Return all the references (text:reference-ref). If name is
2884        provided, returns the references of that name.
2885
2886        Return: list of Element
2887
2888        Arguments:
2889
2890            name -- str or None
2891        """
2892        if name is None:
2893            return self._filtered_elements("descendant::text:reference-ref")
2894        request = f'descendant::text:reference-ref[@text:ref-name="{name}"]'
2895        return self._filtered_elements(request)
2896
2897    # Shapes elements
2898
2899    # Groups
2900
2901    def get_draw_groups(
2902        self,
2903        title: str | None = None,
2904        description: str | None = None,
2905        content: str | None = None,
2906    ) -> list[Element]:
2907        return self._filtered_elements(
2908            "descendant::draw:g",
2909            svg_title=title,
2910            svg_desc=description,
2911            content=content,
2912        )
2913
2914    def get_draw_group(
2915        self,
2916        position: int = 0,
2917        name: str | None = None,
2918        title: str | None = None,
2919        description: str | None = None,
2920        content: str | None = None,
2921    ) -> Element | None:
2922        return self._filtered_element(
2923            "descendant::draw:g",
2924            position,
2925            draw_name=name,
2926            svg_title=title,
2927            svg_desc=description,
2928            content=content,
2929        )
2930
2931    # Lines
2932
2933    def get_draw_lines(
2934        self,
2935        draw_style: str | None = None,
2936        draw_text_style: str | None = None,
2937        content: str | None = None,
2938    ) -> list[Element]:
2939        """Return all the draw lines that match the criteria.
2940
2941        Arguments:
2942
2943            draw_style -- str
2944
2945            draw_text_style -- str
2946
2947            content -- str regex
2948
2949        Return: list of odf_shape
2950        """
2951        return self._filtered_elements(
2952            "descendant::draw:line",
2953            draw_style=draw_style,
2954            draw_text_style=draw_text_style,
2955            content=content,
2956        )
2957
2958    def get_draw_line(
2959        self,
2960        position: int = 0,
2961        id: str | None = None,  # noqa:A002
2962        content: str | None = None,
2963    ) -> Element | None:
2964        """Return the draw line that matches the criteria.
2965
2966        Arguments:
2967
2968            position -- int
2969
2970            id -- str
2971
2972            content -- str regex
2973
2974        Return: odf_shape or None if not found
2975        """
2976        return self._filtered_element(
2977            "descendant::draw:line", position, draw_id=id, content=content
2978        )
2979
2980    # Rectangles
2981
2982    def get_draw_rectangles(
2983        self,
2984        draw_style: str | None = None,
2985        draw_text_style: str | None = None,
2986        content: str | None = None,
2987    ) -> list[Element]:
2988        """Return all the draw rectangles that match the criteria.
2989
2990        Arguments:
2991
2992            draw_style -- str
2993
2994            draw_text_style -- str
2995
2996            content -- str regex
2997
2998        Return: list of odf_shape
2999        """
3000        return self._filtered_elements(
3001            "descendant::draw:rect",
3002            draw_style=draw_style,
3003            draw_text_style=draw_text_style,
3004            content=content,
3005        )
3006
3007    def get_draw_rectangle(
3008        self,
3009        position: int = 0,
3010        id: str | None = None,  # noqa:A002
3011        content: str | None = None,
3012    ) -> Element | None:
3013        """Return the draw rectangle that matches the criteria.
3014
3015        Arguments:
3016
3017            position -- int
3018
3019            id -- str
3020
3021            content -- str regex
3022
3023        Return: odf_shape or None if not found
3024        """
3025        return self._filtered_element(
3026            "descendant::draw:rect", position, draw_id=id, content=content
3027        )
3028
3029    # Ellipse
3030
3031    def get_draw_ellipses(
3032        self,
3033        draw_style: str | None = None,
3034        draw_text_style: str | None = None,
3035        content: str | None = None,
3036    ) -> list[Element]:
3037        """Return all the draw ellipses that match the criteria.
3038
3039        Arguments:
3040
3041            draw_style -- str
3042
3043            draw_text_style -- str
3044
3045            content -- str regex
3046
3047        Return: list of odf_shape
3048        """
3049        return self._filtered_elements(
3050            "descendant::draw:ellipse",
3051            draw_style=draw_style,
3052            draw_text_style=draw_text_style,
3053            content=content,
3054        )
3055
3056    def get_draw_ellipse(
3057        self,
3058        position: int = 0,
3059        id: str | None = None,  # noqa:A002
3060        content: str | None = None,
3061    ) -> Element | None:
3062        """Return the draw ellipse that matches the criteria.
3063
3064        Arguments:
3065
3066            position -- int
3067
3068            id -- str
3069
3070            content -- str regex
3071
3072        Return: odf_shape or None if not found
3073        """
3074        return self._filtered_element(
3075            "descendant::draw:ellipse", position, draw_id=id, content=content
3076        )
3077
3078    # Connectors
3079
3080    def get_draw_connectors(
3081        self,
3082        draw_style: str | None = None,
3083        draw_text_style: str | None = None,
3084        content: str | None = None,
3085    ) -> list[Element]:
3086        """Return all the draw connectors that match the criteria.
3087
3088        Arguments:
3089
3090            draw_style -- str
3091
3092            draw_text_style -- str
3093
3094            content -- str regex
3095
3096        Return: list of odf_shape
3097        """
3098        return self._filtered_elements(
3099            "descendant::draw:connector",
3100            draw_style=draw_style,
3101            draw_text_style=draw_text_style,
3102            content=content,
3103        )
3104
3105    def get_draw_connector(
3106        self,
3107        position: int = 0,
3108        id: str | None = None,  # noqa:A002
3109        content: str | None = None,
3110    ) -> Element | None:
3111        """Return the draw connector that matches the criteria.
3112
3113        Arguments:
3114
3115            position -- int
3116
3117            id -- str
3118
3119            content -- str regex
3120
3121        Return: odf_shape or None if not found
3122        """
3123        return self._filtered_element(
3124            "descendant::draw:connector", position, draw_id=id, content=content
3125        )
3126
3127    def get_orphan_draw_connectors(self) -> list[Element]:
3128        """Return a list of connectors which don't have any shape connected
3129        to them.
3130        """
3131        connectors = []
3132        for connector in self.get_draw_connectors():
3133            start_shape = connector.get_attribute("draw:start-shape")
3134            end_shape = connector.get_attribute("draw:end-shape")
3135            if start_shape is None and end_shape is None:
3136                connectors.append(connector)
3137        return connectors
3138
3139    # Tracked changes and text change
3140
3141    def get_tracked_changes(self) -> Element | None:
3142        """Return the tracked-changes part in the text body."""
3143        return self.get_element("//text:tracked-changes")
3144
3145    def get_changes_ids(self) -> list[Element | Text]:
3146        """Return a list of ids that refers to a change region in the tracked
3147        changes list.
3148        """
3149        # Insertion changes
3150        xpath_query = "descendant::text:change-start/@text:change-id"
3151        # Deletion changes
3152        xpath_query += " | descendant::text:change/@text:change-id"
3153        return self.xpath(xpath_query)
3154
3155    def get_text_change_deletions(self) -> list[Element]:
3156        """Return all the text changes of deletion kind: the tags text:change.
3157        Consider using : get_text_changes()
3158
3159        Return: list of Element
3160        """
3161        return self._filtered_elements("descendant::text:text:change")
3162
3163    def get_text_change_deletion(
3164        self,
3165        position: int = 0,
3166        idx: str | None = None,
3167    ) -> Element | None:
3168        """Return the text change of deletion kind that matches the criteria.
3169        Search only for the tags text:change.
3170        Consider using : get_text_change()
3171
3172        Arguments:
3173
3174            position -- int
3175
3176            idx -- str
3177
3178        Return: Element or None if not found
3179        """
3180        return self._filtered_element(
3181            "descendant::text:change", position, change_id=idx
3182        )
3183
3184    def get_text_change_starts(self) -> list[Element]:
3185        """Return all the text change-start. Search only for the tags
3186        text:change-start.
3187        Consider using : get_text_changes()
3188
3189        Return: list of Element
3190        """
3191        return self._filtered_elements("descendant::text:change-start")
3192
3193    def get_text_change_start(
3194        self,
3195        position: int = 0,
3196        idx: str | None = None,
3197    ) -> Element | None:
3198        """Return the text change-start that matches the criteria. Search
3199        only the tags text:change-start.
3200        Consider using : get_text_change()
3201
3202        Arguments:
3203
3204            position -- int
3205
3206            idx -- str
3207
3208        Return: Element or None if not found
3209        """
3210        return self._filtered_element(
3211            "descendant::text:change-start", position, change_id=idx
3212        )
3213
3214    def get_text_change_ends(self) -> list[Element]:
3215        """Return all the text change-end. Search only the tags
3216        text:change-end.
3217        Consider using : get_text_changes()
3218
3219        Return: list of Element
3220        """
3221        return self._filtered_elements("descendant::text:change-end")
3222
3223    def get_text_change_end(
3224        self,
3225        position: int = 0,
3226        idx: str | None = None,
3227    ) -> Element | None:
3228        """Return the text change-end that matches the criteria. Search only
3229        the tags text:change-end.
3230        Consider using : get_text_change()
3231
3232        Arguments:
3233
3234            position -- int
3235
3236            idx -- str
3237
3238        Return: Element or None if not found
3239        """
3240        return self._filtered_element(
3241            "descendant::text:change-end", position, change_id=idx
3242        )
3243
3244    def get_text_changes(self) -> list[Element]:
3245        """Return all the text changes, either single deletion
3246        (text:change) or start of range of changes (text:change-start).
3247
3248        Return: list of Element
3249        """
3250        request = "descendant::text:change-start | descendant::text:change"
3251        return self._filtered_elements(request)
3252
3253    def get_text_change(
3254        self,
3255        position: int = 0,
3256        idx: str | None = None,
3257    ) -> Element | None:
3258        """Return the text change that matches the criteria. Either single
3259        deletion (text:change) or start of range of changes (text:change-start).
3260        position : index of the element to retrieve if several matches, default
3261        is 0.
3262        idx : change-id of the element.
3263
3264        Arguments:
3265
3266            position -- int
3267
3268            idx -- str
3269
3270        Return: Element or None if not found
3271        """
3272        if idx:
3273            request = (
3274                f'descendant::text:change-start[@text:change-id="{idx}"] '
3275                f'| descendant::text:change[@text:change-id="{idx}"]'
3276            )
3277            return self._filtered_element(request, 0)
3278        request = "descendant::text:change-start | descendant::text:change"
3279        return self._filtered_element(request, position)
3280
3281    # Table Of Content
3282
3283    def get_tocs(self) -> list[Element]:
3284        """Return all the tables of contents.
3285
3286        Return: list of odf_toc
3287        """
3288        return self._filtered_elements("text:table-of-content")
3289
3290    def get_toc(
3291        self,
3292        position: int = 0,
3293        content: str | None = None,
3294    ) -> Element | None:
3295        """Return the table of contents that matches the criteria.
3296
3297        Arguments:
3298
3299            position -- int
3300
3301            content -- str regex
3302
3303        Return: odf_toc or None if not found
3304        """
3305        return self._filtered_element(
3306            "text:table-of-content", position, content=content
3307        )
3308
3309    # Styles
3310
3311    @staticmethod
3312    def _get_style_tagname(family: str | None, is_default: bool = False) -> str:
3313        """Widely match possible tag names given the family (or not)."""
3314        if not family:
3315            tagname = "(style:default-style|*[@style:name]|draw:fill-image|draw:marker)"
3316        elif is_default:
3317            # Default style
3318            tagname = "style:default-style"
3319        else:
3320            tagname = _family_style_tagname(family)
3321            # if famattr:
3322            #    # Include family default style
3323            #    tagname = '(%s|style:default-style)' % tagname
3324            if family in FAMILY_ODF_STD:
3325                # Include family default style
3326                tagname = f"({tagname}|style:default-style)"
3327        return tagname
3328
3329    def get_styles(self, family: str | None = None) -> list[Element]:
3330        # Both common and default styles
3331        tagname = self._get_style_tagname(family)
3332        return self._filtered_elements(tagname, family=family)
3333
3334    def get_style(
3335        self,
3336        family: str,
3337        name_or_element: str | Element | None = None,
3338        display_name: str | None = None,
3339    ) -> Element | None:
3340        """Return the style uniquely identified by the family/name pair. If
3341        the argument is already a style object, it will return it.
3342
3343        If the name is not the internal name but the name you gave in the
3344        desktop application, use display_name instead.
3345
3346        Arguments:
3347
3348            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
3349                      'number'
3350
3351            name_or_element -- str or Style
3352
3353            display_name -- str
3354
3355        Return: odf_style or None if not found
3356        """
3357        if isinstance(name_or_element, Element):
3358            name = self.get_attribute("style:name")
3359            if name is not None:
3360                return name_or_element
3361            else:
3362                raise ValueError(f"Not a odf_style ? {name_or_element!r}")
3363        style_name = name_or_element
3364        is_default = not (style_name or display_name)
3365        tagname = self._get_style_tagname(family, is_default=is_default)
3366        # famattr became None if no "style:family" attribute
3367        if family:
3368            return self._filtered_element(
3369                tagname,
3370                0,
3371                style_name=style_name,
3372                display_name=display_name,
3373                family=family,
3374            )
3375        else:
3376            return self._filtered_element(
3377                tagname,
3378                0,
3379                draw_name=style_name or display_name,
3380                family=family,
3381            )
3382
3383    def _filtered_element(
3384        self,
3385        query_string: str,
3386        position: int,
3387        **kwargs: Any,
3388    ) -> Element | None:
3389        results = self._filtered_elements(query_string, **kwargs)
3390        try:
3391            return results[position]
3392        except IndexError:
3393            return None
3394
3395    def _filtered_elements(
3396        self,
3397        query_string: str,
3398        content: str | None = None,
3399        url: str | None = None,
3400        svg_title: str | None = None,
3401        svg_desc: str | None = None,
3402        dc_creator: str | None = None,
3403        dc_date: datetime | None = None,
3404        **kwargs: Any,
3405    ) -> list[Element]:
3406        query = make_xpath_query(query_string, **kwargs)
3407        elements = self.get_elements(query)
3408        # Filter the elements with the regex (TODO use XPath)
3409        if content is not None:
3410            elements = [element for element in elements if element.match(content)]
3411        if url is not None:
3412            filtered = []
3413            for element in elements:
3414                url_attr = element.get_attribute("xlink:href")
3415                if isinstance(url_attr, str) and search(url, url_attr) is not None:
3416                    filtered.append(element)
3417            elements = filtered
3418        if dc_date is None:
3419            dt_dc_date = None
3420        else:
3421            dt_dc_date = DateTime.encode(dc_date)
3422        for variable, childname in [
3423            (svg_title, "svg:title"),
3424            (svg_desc, "svg:desc"),
3425            (dc_creator, "descendant::dc:creator"),
3426            (dt_dc_date, "descendant::dc:date"),
3427        ]:
3428            if not variable:
3429                continue
3430            filtered = []
3431            for element in elements:
3432                child = element.get_element(childname)
3433                if child and child.match(variable):
3434                    filtered.append(element)
3435            elements = filtered
3436        return elements

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Element(**kwargs: Any)
314    def __init__(self, **kwargs: Any) -> None:
315        tag_or_elem = kwargs.pop("tag_or_elem", None)
316        if tag_or_elem is None:
317            # Instance for newly created object: create new lxml element and
318            # continue by subclass __init__
319            # If the tag key word exists, make a custom element
320            self._do_init = True
321            tag = kwargs.pop("tag", self._tag)
322            self.__element = self.make_etree_element(tag)
323        else:
324            # called with an existing lxml element, sould be a result of
325            # from_tag() casting, do not execute the subclass __init__
326            if not isinstance(tag_or_elem, _Element):
327                raise TypeError(f'"{type(tag_or_elem)}" is not an element node')
328            self._do_init = False
329            self.__element = tag_or_elem
@classmethod
def from_tag(cls, tag_or_elem: str | lxml.etree._Element) -> Element:
337    @classmethod
338    def from_tag(cls, tag_or_elem: str | _Element) -> Element:
339        """Element class and subclass factory.
340
341        Turn an lxml Element or ODF string tag into an ODF XML Element
342        of the relevant class.
343
344        Arguments:
345
346            tag_or_elem -- ODF str tag or lxml.Element
347
348        Return: Element (or subclass) instance
349        """
350        if isinstance(tag_or_elem, str):
351            # assume the argument is a prefix:name tag
352            elem = cls.make_etree_element(tag_or_elem)
353        else:
354            elem = tag_or_elem
355        klass = _class_registry.get(elem.tag, cls)
356        return klass(tag_or_elem=elem)

Element class and subclass factory.

Turn an lxml Element or ODF string tag into an ODF XML Element of the relevant class.

Arguments:

tag_or_elem -- ODF str tag or lxml.Element

Return: Element (or subclass) instance

@classmethod
def from_tag_for_clone( cls: type, tree_element: lxml.etree._Element, cache: tuple | None) -> Element:
358    @classmethod
359    def from_tag_for_clone(
360        cls: type,
361        tree_element: _Element,
362        cache: tuple | None,
363    ) -> Element:
364        tag = to_str(tree_element.tag)
365        klass = _class_registry.get(tag, cls)
366        element = klass(tag_or_elem=tree_element)
367        if cache and element._caching:
368            element._tmap = cache[0]
369            element._cmap = cache[1]
370            if len(cache) == 3:
371                element._rmap = cache[2]
372        return element
@staticmethod
def make_etree_element(tag: str) -> lxml.etree._Element:
374    @staticmethod
375    def make_etree_element(tag: str) -> _Element:
376        if not isinstance(tag, str):
377            raise TypeError(f"Tag is not str: {tag!r}")
378        tag = tag.strip()
379        if not tag:
380            raise ValueError("Tag is empty")
381        if "<" not in tag:
382            # Qualified name
383            # XXX don't build the element from scratch or lxml will pollute with
384            # repeated namespace declarations
385            tag = f"<{tag}/>"
386        # XML fragment
387        root = fromstring(NAMESPACES_XML % str_to_bytes(tag))
388        return root[0]
tag: str
751    @property
752    def tag(self) -> str:
753        """Get/set the underlying xml tag with the given qualified name.
754
755        Warning: direct change of tag does not change the element class.
756
757        Arguments:
758
759            qname -- str (e.g. "text:span")
760        """
761        return _get_prefixed_name(self.__element.tag)

Get/set the underlying xml tag with the given qualified name.

Warning: direct change of tag does not change the element class.

Arguments:

qname -- str (e.g. "text:span")
def elements_repeated_sequence( self, xpath_instance: lxml.etree.XPath, name: str) -> list[tuple[int, int]]:
767    def elements_repeated_sequence(
768        self,
769        xpath_instance: XPath,
770        name: str,
771    ) -> list[tuple[int, int]]:
772        """Utility method for table module."""
773        lxml_tag = _get_lxml_tag_or_name(name)
774        element = self.__element
775        sub_elements = xpath_instance(element)
776        if not isinstance(sub_elements, list):
777            raise TypeError("Bad XPath result.")
778        result: list[tuple[int, int]] = []
779        idx = -1
780        for sub_element in sub_elements:
781            if not isinstance(sub_element, _Element):
782                continue
783            idx += 1
784            value = sub_element.get(lxml_tag)
785            if value is None:
786                result.append((idx, 1))
787                continue
788            try:
789                int_value = int(value)
790            except ValueError:
791                int_value = 1
792            result.append((idx, max(int_value, 1)))
793        return result

Utility method for table module.

def get_elements(self, xpath_query: lxml.etree.XPath | str) -> list[Element]:
795    def get_elements(self, xpath_query: XPath | str) -> list[Element]:
796        cache: tuple | None = None
797        element = self.__element
798        if isinstance(xpath_query, str):
799            new_xpath_query = xpath_compile(xpath_query)
800            result = new_xpath_query(element)
801        else:
802            result = xpath_query(element)
803        if not isinstance(result, list):
804            raise TypeError("Bad XPath result")
805
806        if hasattr(self, "_tmap"):
807            if hasattr(self, "_rmap"):
808                cache = (self._tmap, self._cmap, self._rmap)
809            else:
810                cache = (self._tmap, self._cmap)
811        return [
812            Element.from_tag_for_clone(e, cache)
813            for e in result
814            if isinstance(e, _Element)
815        ]
def get_element( self, xpath_query: lxml.etree.XPath | str) -> Element | None:
819    def get_element(self, xpath_query: XPath | str) -> Element | None:
820        element = self.__element
821        result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES)
822        if result:
823            return Element.from_tag(result[0])  # type:ignore
824        return None
attributes: dict[str, str]
840    @property
841    def attributes(self) -> dict[str, str]:
842        return {
843            _get_prefixed_name(str(key)): str(value)
844            for key, value in self.__element.attrib.items()
845        }
def get_attribute(self, name: str) -> str | bool | None:
847    def get_attribute(self, name: str) -> str | bool | None:
848        """Return the attribute value as type str | bool | None."""
849        element = self.__element
850        lxml_tag = _get_lxml_tag_or_name(name)
851        value = element.get(lxml_tag)
852        if value is None:
853            return None
854        elif value in ("true", "false"):
855            return Boolean.decode(value)
856        return str(value)

Return the attribute value as type str | bool | None.

def get_attribute_integer(self, name: str) -> int | None:
858    def get_attribute_integer(self, name: str) -> int | None:
859        """Return either the attribute as type int, or None."""
860        element = self.__element
861        lxml_tag = _get_lxml_tag_or_name(name)
862        value = element.get(lxml_tag)
863        if value is None:
864            return None
865        try:
866            return int(value)
867        except ValueError:
868            return None

Return either the attribute as type int, or None.

def get_attribute_string(self, name: str) -> str | None:
870    def get_attribute_string(self, name: str) -> str | None:
871        """Return either the attribute as type str, or None."""
872        element = self.__element
873        lxml_tag = _get_lxml_tag_or_name(name)
874        value = element.get(lxml_tag)
875        if value is None:
876            return None
877        return str(value)

Return either the attribute as type str, or None.

def set_attribute(self, name: str, value: bool | str | None) -> None:
879    def set_attribute(self, name: str, value: bool | str | None) -> None:
880        element = self.__element
881        lxml_tag = _get_lxml_tag_or_name(name)
882        if isinstance(value, bool):
883            value = Boolean.encode(value)
884        elif value is None:
885            with contextlib.suppress(KeyError):
886                del element.attrib[lxml_tag]
887            return
888        element.set(lxml_tag, str(value))
def set_style_attribute(self, name: str, value: Element | str) -> None:
890    def set_style_attribute(self, name: str, value: Element | str) -> None:
891        """Shortcut to accept a style object as a value."""
892        if isinstance(value, Element):
893            value = str(value.name)  # type:ignore
894        return self.set_attribute(name, value)

Shortcut to accept a style object as a value.

def del_attribute(self, name: str) -> None:
896    def del_attribute(self, name: str) -> None:
897        element = self.__element
898        lxml_tag = _get_lxml_tag_or_name(name)
899        del element.attrib[lxml_tag]
text: str
901    @property
902    def text(self) -> str:
903        """Get / set the text content of the element."""
904        return self.__element.text or ""

Get / set the text content of the element.

text_recursive: str
915    @property
916    def text_recursive(self) -> str:
917        return "".join(str(x) for x in self.__element.itertext())
tail: str | None
919    @property
920    def tail(self) -> str | None:
921        """Get / set the text immediately following the element."""
922        return self.__element.tail

Get / set the text immediately following the element.

def search(self, pattern: str) -> int | None:
928    def search(self, pattern: str) -> int | None:
929        """Return the first position of the pattern in the text content of
930        the element, or None if not found.
931
932        Python regular expression syntax applies.
933
934        Arguments:
935
936            pattern -- str
937
938        Return: int or None
939        """
940        match = re.search(pattern, self.text_recursive)
941        if match is None:
942            return None
943        return match.start()

Return the first position of the pattern in the text content of the element, or None if not found.

Python regular expression syntax applies.

Arguments:

pattern -- str

Return: int or None

def match(self, pattern: str) -> bool:
945    def match(self, pattern: str) -> bool:
946        """return True if the pattern is found one or more times anywhere in
947        the text content of the element.
948
949        Python regular expression syntax applies.
950
951        Arguments:
952
953            pattern -- str
954
955        Return: bool
956        """
957        return self.search(pattern) is not None

return True if the pattern is found one or more times anywhere in the text content of the element.

Python regular expression syntax applies.

Arguments:

pattern -- str

Return: bool

def replace(self, pattern: str, new: str | None = None) -> int:
959    def replace(self, pattern: str, new: str | None = None) -> int:
960        """Replace the pattern with the given text, or delete if text is an
961        empty string, and return the number of replacements. By default, only
962        return the number of occurences that would be replaced.
963
964        It cannot replace patterns found across several element, like a word
965        split into two consecutive spans.
966
967        Python regular expression syntax applies.
968
969        Arguments:
970
971            pattern -- str
972
973            new -- str
974
975        Return: int
976        """
977        if not isinstance(pattern, str):
978            # Fail properly if the pattern is an non-ascii bytestring
979            pattern = str(pattern)
980        cpattern = re.compile(pattern)
981        count = 0
982        for text in self.xpath("descendant::text()"):
983            if new is None:
984                count += len(cpattern.findall(str(text)))
985            else:
986                new_text, number = cpattern.subn(new, str(text))
987                container = text.parent
988                if text.is_text():  # type: ignore
989                    container.text = new_text  # type: ignore
990                else:
991                    container.tail = new_text  # type: ignore
992                count += number
993        return count

Replace the pattern with the given text, or delete if text is an empty string, and return the number of replacements. By default, only return the number of occurences that would be replaced.

It cannot replace patterns found across several element, like a word split into two consecutive spans.

Python regular expression syntax applies.

Arguments:

pattern -- str

new -- str

Return: int

root: Element
 995    @property
 996    def root(self) -> Element:
 997        element = self.__element
 998        tree = element.getroottree()
 999        root = tree.getroot()
1000        return Element.from_tag(root)
parent: Element | None
1002    @property
1003    def parent(self) -> Element | None:
1004        element = self.__element
1005        parent = element.getparent()
1006        if parent is None:
1007            # Already at root
1008            return None
1009        return Element.from_tag(parent)
is_bound: bool
1011    @property
1012    def is_bound(self) -> bool:
1013        return self.parent is not None
children: list[Element]
1029    @property
1030    def children(self) -> list[Element]:
1031        element = self.__element
1032        return [
1033            Element.from_tag(e)
1034            for e in element.iterchildren()
1035            if isinstance(e, _Element)
1036        ]
def index(self, child: Element) -> int:
1038    def index(self, child: Element) -> int:
1039        """Return the position of the child in this element.
1040
1041        Inspired by lxml
1042        """
1043        return self.__element.index(child.__element)

Return the position of the child in this element.

Inspired by lxml

text_content: str
1045    @property
1046    def text_content(self) -> str:
1047        """Get / set the text of the embedded paragraph, including embeded
1048        annotations, cells...
1049
1050        Set create a paragraph if missing
1051        """
1052        return "\n".join(
1053            child.text_recursive for child in self.get_elements("descendant::text:p")
1054        )

Get / set the text of the embedded paragraph, including embeded annotations, cells...

Set create a paragraph if missing

def is_empty(self) -> bool:
1086    def is_empty(self) -> bool:
1087        """Check if the element is empty : no text, no children, no tail.
1088
1089        Return: Boolean
1090        """
1091        element = self.__element
1092        if element.tail is not None:
1093            return False
1094        if element.text is not None:
1095            return False
1096        if list(element.iterchildren()):
1097            return False
1098        return True

Check if the element is empty : no text, no children, no tail.

Return: Boolean

def get_between( self, tag1: Element, tag2: Element, as_text: bool = False, clean: bool = True, no_header: bool = True) -> list | Element | str:
1243    def get_between(
1244        self,
1245        tag1: Element,
1246        tag2: Element,
1247        as_text: bool = False,
1248        clean: bool = True,
1249        no_header: bool = True,
1250    ) -> list | Element | str:
1251        """Returns elements between tag1 and tag2, tag1 and tag2 shall
1252        be unique and having an id attribute.
1253        (WARN: buggy if tag1/tag2 defines a malformed odf xml.)
1254        If as_text is True: returns the text content.
1255        If clean is True: suppress unwanted tags (deletions marks, ...)
1256        If no_header is True: existing text:h are changed in text:p
1257        By default: returns a list of Element, cleaned and without headers.
1258
1259        Implementation and standard retrictions:
1260        Only text:h and text:p sould be 'cut' by an insert tag, so inner parts
1261        of insert tags are:
1262
1263            - any text:h, text:p or sub tag of these
1264
1265            - some text, part of a parent text:h or text:p
1266
1267        Arguments:
1268
1269            tag1 -- Element
1270
1271            tag2 -- Element
1272
1273            as_text -- boolean
1274
1275            clean -- boolean
1276
1277            no_header -- boolean
1278
1279        Return: list of odf_paragraph or odf_header
1280        """
1281        inner = self._get_between_base(tag1, tag2)
1282
1283        if clean:
1284            clean_tags = (
1285                "text:change",
1286                "text:change-start",
1287                "text:change-end",
1288                "text:reference-mark",
1289                "text:reference-mark-start",
1290                "text:reference-mark-end",
1291            )
1292            request_self = " | ".join(["self::%s" % c for c in clean_tags])
1293            inner = [e for e in inner if not e.xpath(request_self)]
1294            request = " | ".join([f"descendant::{tag}" for tag in clean_tags])
1295            for element in inner:
1296                to_del = element.xpath(request)
1297                for elem in to_del:
1298                    if isinstance(elem, Element):
1299                        element.delete(elem)
1300        if no_header:  # crude replace t:h by t:p
1301            new_inner = []
1302            for element in inner:
1303                if element.tag == "text:h":
1304                    children = element.children
1305                    text = element.__element.text
1306                    para = Element.from_tag("text:p")
1307                    para.text = text or ""
1308                    for c in children:
1309                        para.append(c)
1310                    new_inner.append(para)
1311                else:
1312                    new_inner.append(element)
1313            inner = new_inner
1314        if as_text:
1315            return "\n".join([e.get_formatted_text() for e in inner])
1316        else:
1317            return inner

Returns elements between tag1 and tag2, tag1 and tag2 shall be unique and having an id attribute. (WARN: buggy if tag1/tag2 defines a malformed odf xml.) If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and without headers.

Implementation and standard retrictions: Only text:h and text:p sould be 'cut' by an insert tag, so inner parts of insert tags are:

- any text:h, text:p or sub tag of these

- some text, part of a parent text:h or text:p

Arguments:

tag1 -- Element

tag2 -- Element

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list of odf_paragraph or odf_header

def insert( self, element: Element, xmlposition: int | None = None, position: int | None = None, start: bool = False) -> None:
1319    def insert(
1320        self,
1321        element: Element,
1322        xmlposition: int | None = None,
1323        position: int | None = None,
1324        start: bool = False,
1325    ) -> None:
1326        """Insert an element relatively to ourself.
1327
1328        Insert either using DOM vocabulary or by numeric position.
1329        If text start is True, insert the element before any existing text.
1330
1331        Position start at 0.
1332
1333        Arguments:
1334
1335            element -- Element
1336
1337            xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
1338                           or PREV_SIBLING
1339
1340            start -- Boolean
1341
1342            position -- int
1343        """
1344        # child_tag = element.tag
1345        current = self.__element
1346        _element = element.__element
1347        if start:
1348            text = current.text
1349            if text is not None:
1350                current.text = None
1351                tail = _element.tail
1352                if tail is None:
1353                    tail = text
1354                else:
1355                    tail = tail + text
1356                _element.tail = tail
1357            position = 0
1358        if position is not None:
1359            current.insert(position, _element)
1360        elif xmlposition is FIRST_CHILD:
1361            current.insert(0, _element)
1362        elif xmlposition is LAST_CHILD:
1363            current.append(_element)
1364        elif xmlposition is NEXT_SIBLING:
1365            parent = current.getparent()
1366            index = parent.index(current)  # type: ignore
1367            parent.insert(index + 1, _element)  # type: ignore
1368        elif xmlposition is PREV_SIBLING:
1369            parent = current.getparent()
1370            index = parent.index(current)  # type: ignore
1371            parent.insert(index, _element)  # type: ignore
1372        else:
1373            raise ValueError("(xml)position must be defined")

Insert an element relatively to ourself.

Insert either using DOM vocabulary or by numeric position. If text start is True, insert the element before any existing text.

Position start at 0.

Arguments:

element -- Element

xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
               or PREV_SIBLING

start -- Boolean

position -- int
def extend( self, odf_elements: collections.abc.Iterable[Element]) -> None:
1375    def extend(self, odf_elements: Iterable[Element]) -> None:
1376        """Fast append elements at the end of ourself using extend."""
1377        if odf_elements:
1378            current = self.__element
1379            elements = [element.__element for element in odf_elements]
1380            current.extend(elements)

Fast append elements at the end of ourself using extend.

def append(self, str_or_element: str | Element) -> None:
1382    def append(self, str_or_element: str | Element) -> None:
1383        """Insert element or text in the last position."""
1384        current = self.__element
1385        if isinstance(str_or_element, str):
1386            # Has children ?
1387            children = list(current.iterchildren())
1388            if children:
1389                # Append to tail of the last child
1390                last_child = children[-1]
1391                text = last_child.tail
1392                text = text if text is not None else ""
1393                text += str_or_element
1394                last_child.tail = text
1395            else:
1396                # Append to text of the element
1397                text = current.text
1398                text = text if text is not None else ""
1399                text += str_or_element
1400                current.text = text
1401        elif isinstance(str_or_element, Element):
1402            current.append(str_or_element.__element)
1403        else:
1404            raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')

Insert element or text in the last position.

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
1406    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
1407        """Delete the given element from the XML tree. If no element is given,
1408        "self" is deleted. The XML library may allow to continue to use an
1409        element now "orphan" as long as you have a reference to it.
1410
1411        if keep_tail is True (default), the tail text is not erased.
1412
1413        Arguments:
1414
1415            child -- Element
1416
1417            keep_tail -- boolean (default to True), True for most usages.
1418        """
1419        if child is None:
1420            parent = self.parent
1421            if parent is None:
1422                raise ValueError(f"Can't delete the root element\n{self.serialize()}")
1423            child = self
1424        else:
1425            parent = self
1426        if keep_tail and child.__element.tail is not None:
1427            current = child.__element
1428            tail = str(current.tail)
1429            current.tail = None
1430            prev = current.getprevious()
1431            if prev is not None:
1432                if prev.tail is None:
1433                    prev.tail = tail
1434                else:
1435                    prev.tail += tail
1436            else:
1437                if parent.__element.text is None:
1438                    parent.__element.text = tail
1439                else:
1440                    parent.__element.text += tail
1441        parent.__element.remove(child.__element)

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

if keep_tail is True (default), the tail text is not erased.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
def replace_element( self, old_element: Element, new_element: Element) -> None:
1443    def replace_element(self, old_element: Element, new_element: Element) -> None:
1444        """Replaces in place a sub element with the element passed as second
1445        argument.
1446
1447        Warning : no clone for old element.
1448        """
1449        current = self.__element
1450        current.replace(old_element.__element, new_element.__element)

Replaces in place a sub element with the element passed as second argument.

Warning : no clone for old element.

def strip_elements( self, sub_elements: Element | collections.abc.Iterable[Element]) -> Element | list:
1452    def strip_elements(
1453        self,
1454        sub_elements: Element | Iterable[Element],
1455    ) -> Element | list:
1456        """Remove the tags of provided elements, keeping inner childs and text.
1457
1458        Return : the striped element.
1459
1460        Warning : no clone in sub_elements list.
1461
1462        Arguments:
1463
1464            sub_elements -- Element or list of Element
1465        """
1466        if not sub_elements:
1467            return self
1468        if isinstance(sub_elements, Element):
1469            sub_elements = (sub_elements,)
1470        replacer = _get_lxml_tag("text:this-will-be-removed")
1471        for element in sub_elements:
1472            element.__element.tag = replacer
1473        strip = ("text:this-will-be-removed",)
1474        return self.strip_tags(strip=strip, default=None)

Remove the tags of provided elements, keeping inner childs and text.

Return : the striped element.

Warning : no clone in sub_elements list.

Arguments:

sub_elements -- Element or list of Element
def strip_tags( self, strip: collections.abc.Iterable[str] | None = None, protect: collections.abc.Iterable[str] | None = None, default: str | None = 'text:p') -> Element | list:
1476    def strip_tags(
1477        self,
1478        strip: Iterable[str] | None = None,
1479        protect: Iterable[str] | None = None,
1480        default: str | None = "text:p",
1481    ) -> Element | list:
1482        """Remove the tags listed in strip, recursively, keeping inner childs
1483        and text. Tags listed in protect stop the removal one level depth. If
1484        the first level element is stripped, default is used to embed the
1485        content in the default element. If default is None and first level is
1486        striped, a list of text and children is returned. Return : the striped
1487        element.
1488
1489        strip_tags should be used by on purpose methods (strip_span ...)
1490        (Method name taken from lxml).
1491
1492        Arguments:
1493
1494            strip -- iterable list of str odf tags, or None
1495
1496            protect -- iterable list of str odf tags, or None
1497
1498            default -- str odf tag, or None
1499
1500        Return:
1501
1502            Element.
1503        """
1504        if not strip:
1505            return self
1506        if not protect:
1507            protect = ()
1508        protected = False
1509        element, modified = Element._strip_tags(self, strip, protect, protected)
1510        if modified and isinstance(element, list) and default:
1511            new = Element.from_tag(default)
1512            for content in element:
1513                if isinstance(content, Element):
1514                    new.append(content)
1515                else:
1516                    new.text = content
1517            element = new
1518        return element

Remove the tags listed in strip, recursively, keeping inner childs and text. Tags listed in protect stop the removal one level depth. If the first level element is stripped, default is used to embed the content in the default element. If default is None and first level is striped, a list of text and children is returned. Return : the striped element.

strip_tags should be used by on purpose methods (strip_span ...) (Method name taken from lxml).

Arguments:

strip -- iterable list of str odf tags, or None

protect -- iterable list of str odf tags, or None

default -- str odf tag, or None

Return:

Element.
def xpath( self, xpath_query: str) -> list[Element | Text]:
1574    def xpath(self, xpath_query: str) -> list[Element | Text]:
1575        """Apply XPath query to the element and its subtree. Return list of
1576        Element or Text instances translated from the nodes found.
1577        """
1578        element = self.__element
1579        xpath_instance = xpath_compile(xpath_query)
1580        elements = xpath_instance(element)
1581        result: list[Element | Text] = []
1582        if hasattr(elements, "__iter__"):
1583            for obj in elements:  # type: ignore
1584                if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)):
1585                    result.append(Text(obj))
1586                elif isinstance(obj, _Element):
1587                    result.append(Element.from_tag(obj))
1588                # else:
1589                #     result.append(obj)
1590        return result

Apply XPath query to the element and its subtree. Return list of Element or Text instances translated from the nodes found.

def clear(self) -> None:
1592    def clear(self) -> None:
1593        """Remove text, children and attributes from the element."""
1594        self.__element.clear()
1595        if hasattr(self, "_tmap"):
1596            self._tmap: list[int] = []
1597        if hasattr(self, "_cmap"):
1598            self._cmap: list[int] = []
1599        if hasattr(self, "_rmap"):
1600            self._rmap: list[int] = []
1601        if hasattr(self, "_indexes"):
1602            remember = False
1603            if "_rmap" in self._indexes:
1604                remember = True
1605            self._indexes: dict[str, dict] = {}
1606            self._indexes["_cmap"] = {}
1607            self._indexes["_tmap"] = {}
1608            if remember:
1609                self._indexes["_rmap"] = {}

Remove text, children and attributes from the element.

clone: Element
1611    @property
1612    def clone(self) -> Element:
1613        clone = deepcopy(self.__element)
1614        root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES)
1615        root.append(clone)
1616        return self.from_tag(clone)
1617
1618        # slow data = tostring(self.__element, encoding='unicode')
1619        # return self.from_tag(data)
def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1626    def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1627        """Return text serialization of XML element."""
1628        # This copy bypasses serialization side-effects in lxml
1629        native = deepcopy(self.__element)
1630        data = tostring(
1631            native, with_tail=False, pretty_print=pretty, encoding="unicode"
1632        )
1633        if with_ns:
1634            return data
1635        # Remove namespaces
1636        return self._strip_namespaces(data)

Return text serialization of XML element.

document_body: Element | None
1640    @property
1641    def document_body(self) -> Element | None:
1642        """Return the document body : 'office:body'"""
1643        return self.get_element("//office:body/*[1]")

Return the document body : 'office:body'

def get_formatted_text(self, context: dict | None = None) -> str:
1658    def get_formatted_text(self, context: dict | None = None) -> str:
1659        """This function should return a beautiful version of the text."""
1660        return ""

This function should return a beautiful version of the text.

def get_styled_elements(self, name: str = '') -> list[Element]:
1662    def get_styled_elements(self, name: str = "") -> list[Element]:
1663        """Brute-force to find paragraphs, tables, etc. using the given style
1664        name (or all by default).
1665
1666        Arguments:
1667
1668            name -- str
1669
1670        Return: list
1671        """
1672        # FIXME incomplete (and possibly inaccurate)
1673        return (
1674            self._filtered_elements("descendant::*", text_style=name)
1675            + self._filtered_elements("descendant::*", draw_style=name)
1676            + self._filtered_elements("descendant::*", draw_text_style=name)
1677            + self._filtered_elements("descendant::*", table_style=name)
1678            + self._filtered_elements("descendant::*", page_layout=name)
1679            + self._filtered_elements("descendant::*", master_page=name)
1680            + self._filtered_elements("descendant::*", parent_style=name)
1681        )

Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).

Arguments:

name -- str

Return: list

dc_creator: str | None
1700    @property
1701    def dc_creator(self) -> str | None:
1702        """Get dc:creator value.
1703
1704        Return: str (or None if inexistant)
1705        """
1706        return self._get_inner_text("dc:creator")

Get dc:creator value.

Return: str (or None if inexistant)

dc_date: datetime.datetime | None
1718    @property
1719    def dc_date(self) -> datetime | None:
1720        """Get the dc:date value.
1721
1722        Return: datetime (or None if inexistant)
1723        """
1724        date = self._get_inner_text("dc:date")
1725        if date is None:
1726            return None
1727        return DateTime.decode(date)

Get the dc:date value.

Return: datetime (or None if inexistant)

svg_title: str | None
1741    @property
1742    def svg_title(self) -> str | None:
1743        return self._get_inner_text("svg:title")
svg_description: str | None
1749    @property
1750    def svg_description(self) -> str | None:
1751        return self._get_inner_text("svg:desc")
def get_sections( self, style: str | None = None, content: str | None = None) -> list[Element]:
1759    def get_sections(
1760        self,
1761        style: str | None = None,
1762        content: str | None = None,
1763    ) -> list[Element]:
1764        """Return all the sections that match the criteria.
1765
1766        Arguments:
1767
1768            style -- str
1769
1770            content -- str regex
1771
1772        Return: list of Element
1773        """
1774        return self._filtered_elements(
1775            "text:section", text_style=style, content=content
1776        )

Return all the sections that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Element

def get_section( self, position: int = 0, content: str | None = None) -> Element | None:
1778    def get_section(
1779        self,
1780        position: int = 0,
1781        content: str | None = None,
1782    ) -> Element | None:
1783        """Return the section that matches the criteria.
1784
1785        Arguments:
1786
1787            position -- int
1788
1789            content -- str regex
1790
1791        Return: Element or None if not found
1792        """
1793        return self._filtered_element(
1794            "descendant::text:section", position, content=content
1795        )

Return the section that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Element or None if not found

def get_paragraphs( self, style: str | None = None, content: str | None = None) -> list[Element]:
1799    def get_paragraphs(
1800        self,
1801        style: str | None = None,
1802        content: str | None = None,
1803    ) -> list[Element]:
1804        """Return all the paragraphs that match the criteria.
1805
1806        Arguments:
1807
1808            style -- str
1809
1810            content -- str regex
1811
1812        Return: list of Paragraph
1813        """
1814        return self._filtered_elements(
1815            "descendant::text:p", text_style=style, content=content
1816        )

Return all the paragraphs that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Paragraph

def get_paragraph( self, position: int = 0, content: str | None = None) -> Element | None:
1818    def get_paragraph(
1819        self,
1820        position: int = 0,
1821        content: str | None = None,
1822    ) -> Element | None:
1823        """Return the paragraph that matches the criteria.
1824
1825        Arguments:
1826
1827            position -- int
1828
1829            content -- str regex
1830
1831        Return: Paragraph or None if not found
1832        """
1833        return self._filtered_element("descendant::text:p", position, content=content)

Return the paragraph that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Paragraph or None if not found

def get_spans( self, style: str | None = None, content: str | None = None) -> list[Element]:
1837    def get_spans(
1838        self,
1839        style: str | None = None,
1840        content: str | None = None,
1841    ) -> list[Element]:
1842        """Return all the spans that match the criteria.
1843
1844        Arguments:
1845
1846            style -- str
1847
1848            content -- str regex
1849
1850        Return: list of Span
1851        """
1852        return self._filtered_elements(
1853            "descendant::text:span", text_style=style, content=content
1854        )

Return all the spans that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Span

def get_span( self, position: int = 0, content: str | None = None) -> Element | None:
1856    def get_span(
1857        self,
1858        position: int = 0,
1859        content: str | None = None,
1860    ) -> Element | None:
1861        """Return the span that matches the criteria.
1862
1863        Arguments:
1864
1865            position -- int
1866
1867            content -- str regex
1868
1869        Return: Span or None if not found
1870        """
1871        return self._filtered_element(
1872            "descendant::text:span", position, content=content
1873        )

Return the span that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Span or None if not found

def get_headers( self, style: str | None = None, outline_level: str | None = None, content: str | None = None) -> list[Element]:
1877    def get_headers(
1878        self,
1879        style: str | None = None,
1880        outline_level: str | None = None,
1881        content: str | None = None,
1882    ) -> list[Element]:
1883        """Return all the Headers that match the criteria.
1884
1885        Arguments:
1886
1887            style -- str
1888
1889            content -- str regex
1890
1891        Return: list of Header
1892        """
1893        return self._filtered_elements(
1894            "descendant::text:h",
1895            text_style=style,
1896            outline_level=outline_level,
1897            content=content,
1898        )

Return all the Headers that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Header

def get_header( self, position: int = 0, outline_level: str | None = None, content: str | None = None) -> Element | None:
1900    def get_header(
1901        self,
1902        position: int = 0,
1903        outline_level: str | None = None,
1904        content: str | None = None,
1905    ) -> Element | None:
1906        """Return the Header that matches the criteria.
1907
1908        Arguments:
1909
1910            position -- int
1911
1912            content -- str regex
1913
1914        Return: Header or None if not found
1915        """
1916        return self._filtered_element(
1917            "descendant::text:h",
1918            position,
1919            outline_level=outline_level,
1920            content=content,
1921        )

Return the Header that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Header or None if not found

def get_lists( self, style: str | None = None, content: str | None = None) -> list[Element]:
1925    def get_lists(
1926        self,
1927        style: str | None = None,
1928        content: str | None = None,
1929    ) -> list[Element]:
1930        """Return all the lists that match the criteria.
1931
1932        Arguments:
1933
1934            style -- str
1935
1936            content -- str regex
1937
1938        Return: list of List
1939        """
1940        return self._filtered_elements(
1941            "descendant::text:list", text_style=style, content=content
1942        )

Return all the lists that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of List

def get_list( self, position: int = 0, content: str | None = None) -> Element | None:
1944    def get_list(
1945        self,
1946        position: int = 0,
1947        content: str | None = None,
1948    ) -> Element | None:
1949        """Return the list that matches the criteria.
1950
1951        Arguments:
1952
1953            position -- int
1954
1955            content -- str regex
1956
1957        Return: List or None if not found
1958        """
1959        return self._filtered_element(
1960            "descendant::text:list", position, content=content
1961        )

Return the list that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: List or None if not found

def get_frames( self, presentation_class: str | None = None, style: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> list[Element]:
1965    def get_frames(
1966        self,
1967        presentation_class: str | None = None,
1968        style: str | None = None,
1969        title: str | None = None,
1970        description: str | None = None,
1971        content: str | None = None,
1972    ) -> list[Element]:
1973        """Return all the frames that match the criteria.
1974
1975        Arguments:
1976
1977            presentation_class -- str
1978
1979            style -- str
1980
1981            title -- str regex
1982
1983            description -- str regex
1984
1985            content -- str regex
1986
1987        Return: list of Frame
1988        """
1989        return self._filtered_elements(
1990            "descendant::draw:frame",
1991            presentation_class=presentation_class,
1992            draw_style=style,
1993            svg_title=title,
1994            svg_desc=description,
1995            content=content,
1996        )

Return all the frames that match the criteria.

Arguments:

presentation_class -- str

style -- str

title -- str regex

description -- str regex

content -- str regex

Return: list of Frame

def get_frame( self, position: int = 0, name: str | None = None, presentation_class: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> Element | None:
1998    def get_frame(
1999        self,
2000        position: int = 0,
2001        name: str | None = None,
2002        presentation_class: str | None = None,
2003        title: str | None = None,
2004        description: str | None = None,
2005        content: str | None = None,
2006    ) -> Element | None:
2007        """Return the section that matches the criteria.
2008
2009        Arguments:
2010
2011            position -- int
2012
2013            name -- str
2014
2015            presentation_class -- str
2016
2017            title -- str regex
2018
2019            description -- str regex
2020
2021            content -- str regex
2022
2023        Return: Frame or None if not found
2024        """
2025        return self._filtered_element(
2026            "descendant::draw:frame",
2027            position,
2028            draw_name=name,
2029            presentation_class=presentation_class,
2030            svg_title=title,
2031            svg_desc=description,
2032            content=content,
2033        )

Return the section that matches the criteria.

Arguments:

position -- int

name -- str

presentation_class -- str

title -- str regex

description -- str regex

content -- str regex

Return: Frame or None if not found

def get_images( self, style: str | None = None, url: str | None = None, content: str | None = None) -> list[Element]:
2037    def get_images(
2038        self,
2039        style: str | None = None,
2040        url: str | None = None,
2041        content: str | None = None,
2042    ) -> list[Element]:
2043        """Return all the images matching the criteria.
2044
2045        Arguments:
2046
2047            style -- str
2048
2049            url -- str regex
2050
2051            content -- str regex
2052
2053        Return: list of Element
2054        """
2055        return self._filtered_elements(
2056            "descendant::draw:image", text_style=style, url=url, content=content
2057        )

Return all the images matching the criteria.

Arguments:

style -- str

url -- str regex

content -- str regex

Return: list of Element

def get_image( self, position: int = 0, name: str | None = None, url: str | None = None, content: str | None = None) -> Element | None:
2059    def get_image(
2060        self,
2061        position: int = 0,
2062        name: str | None = None,
2063        url: str | None = None,
2064        content: str | None = None,
2065    ) -> Element | None:
2066        """Return the image matching the criteria.
2067
2068        Arguments:
2069
2070            position -- int
2071
2072            name -- str
2073
2074            url -- str regex
2075
2076            content -- str regex
2077
2078        Return: Element or None if not found
2079        """
2080        # The frame is holding the name
2081        if name is not None:
2082            frame = self._filtered_element(
2083                "descendant::draw:frame", position, draw_name=name
2084            )
2085            if frame is None:
2086                return None
2087            # The name is supposedly unique
2088            return frame.get_element("draw:image")
2089        return self._filtered_element(
2090            "descendant::draw:image", position, url=url, content=content
2091        )

Return the image matching the criteria.

Arguments:

position -- int

name -- str

url -- str regex

content -- str regex

Return: Element or None if not found

def get_tables( self, style: str | None = None, content: str | None = None) -> list[Element]:
2095    def get_tables(
2096        self,
2097        style: str | None = None,
2098        content: str | None = None,
2099    ) -> list[Element]:
2100        """Return all the tables that match the criteria.
2101
2102        Arguments:
2103
2104            style -- str
2105
2106            content -- str regex
2107
2108        Return: list of Table
2109        """
2110        return self._filtered_elements(
2111            "descendant::table:table", table_style=style, content=content
2112        )

Return all the tables that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Table

def get_table( self, position: int = 0, name: str | None = None, content: str | None = None) -> Element | None:
2114    def get_table(
2115        self,
2116        position: int = 0,
2117        name: str | None = None,
2118        content: str | None = None,
2119    ) -> Element | None:
2120        """Return the table that matches the criteria.
2121
2122        Arguments:
2123
2124            position -- int
2125
2126            name -- str
2127
2128            content -- str regex
2129
2130        Return: Table or None if not found
2131        """
2132        if name is None and content is None:
2133            result = self._filtered_element("descendant::table:table", position)
2134        else:
2135            result = self._filtered_element(
2136                "descendant::table:table",
2137                position,
2138                table_name=name,
2139                content=content,
2140            )
2141        return result

Return the table that matches the criteria.

Arguments:

position -- int

name -- str

content -- str regex

Return: Table or None if not found

def get_named_ranges(self) -> list[Element]:
2145    def get_named_ranges(self) -> list[Element]:
2146        """Return all the tables named ranges.
2147
2148        Return: list of odf_named_range
2149        """
2150        named_ranges = self.get_elements(
2151            "descendant::table:named-expressions/table:named-range"
2152        )
2153        return named_ranges

Return all the tables named ranges.

Return: list of odf_named_range

def get_named_range(self, name: str) -> Element | None:
2155    def get_named_range(self, name: str) -> Element | None:
2156        """Return the named range of specified name, or None if not found.
2157
2158        Arguments:
2159
2160            name -- str
2161
2162        Return: NamedRange
2163        """
2164        named_range = self.get_elements(
2165            f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]'
2166        )
2167        if named_range:
2168            return named_range[0]
2169        else:
2170            return None

Return the named range of specified name, or None if not found.

Arguments:

name -- str

Return: NamedRange

def append_named_range(self, named_range: Element) -> None:
2172    def append_named_range(self, named_range: Element) -> None:
2173        """Append the named range to the spreadsheet, replacing existing named
2174        range of same name if any.
2175
2176        Arguments:
2177
2178            named_range --  NamedRange
2179        """
2180        if self.tag != "office:spreadsheet":
2181            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2182        named_expressions = self.get_element("table:named-expressions")
2183        if not named_expressions:
2184            named_expressions = Element.from_tag("table:named-expressions")
2185            self.append(named_expressions)
2186        # exists ?
2187        current = named_expressions.get_element(
2188            f'table:named-range[@table:name="{named_range.name}"][1]'  # type:ignore
2189        )
2190        if current:
2191            named_expressions.delete(current)
2192        named_expressions.append(named_range)

Append the named range to the spreadsheet, replacing existing named range of same name if any.

Arguments:

named_range --  NamedRange
def delete_named_range(self, name: str) -> None:
2194    def delete_named_range(self, name: str) -> None:
2195        """Delete the Named Range of specified name from the spreadsheet.
2196
2197        Arguments:
2198
2199            name -- str
2200        """
2201        if self.tag != "office:spreadsheet":
2202            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2203        named_range = self.get_named_range(name)
2204        if not named_range:
2205            return
2206        named_range.delete()
2207        named_expressions = self.get_element("table:named-expressions")
2208        if not named_expressions:
2209            return
2210        element = named_expressions.__element
2211        children = list(element.iterchildren())
2212        if not children:
2213            self.delete(named_expressions)

Delete the Named Range of specified name from the spreadsheet.

Arguments:

name -- str
def get_notes( self, note_class: str | None = None, content: str | None = None) -> list[Element]:
2217    def get_notes(
2218        self,
2219        note_class: str | None = None,
2220        content: str | None = None,
2221    ) -> list[Element]:
2222        """Return all the notes that match the criteria.
2223
2224        Arguments:
2225
2226            note_class -- 'footnote' or 'endnote'
2227
2228            content -- str regex
2229
2230        Return: list of Note
2231        """
2232        return self._filtered_elements(
2233            "descendant::text:note", note_class=note_class, content=content
2234        )

Return all the notes that match the criteria.

Arguments:

note_class -- 'footnote' or 'endnote'

content -- str regex

Return: list of Note

def get_note( self, position: int = 0, note_id: str | None = None, note_class: str | None = None, content: str | None = None) -> Element | None:
2236    def get_note(
2237        self,
2238        position: int = 0,
2239        note_id: str | None = None,
2240        note_class: str | None = None,
2241        content: str | None = None,
2242    ) -> Element | None:
2243        """Return the note that matches the criteria.
2244
2245        Arguments:
2246
2247            position -- int
2248
2249            note_id -- str
2250
2251            note_class -- 'footnote' or 'endnote'
2252
2253            content -- str regex
2254
2255        Return: Note or None if not found
2256        """
2257        return self._filtered_element(
2258            "descendant::text:note",
2259            position,
2260            text_id=note_id,
2261            note_class=note_class,
2262            content=content,
2263        )

Return the note that matches the criteria.

Arguments:

position -- int

note_id -- str

note_class -- 'footnote' or 'endnote'

content -- str regex

Return: Note or None if not found

def get_annotations( self, creator: str | None = None, start_date: datetime.datetime | None = None, end_date: datetime.datetime | None = None, content: str | None = None) -> list[Element]:
2267    def get_annotations(
2268        self,
2269        creator: str | None = None,
2270        start_date: datetime | None = None,
2271        end_date: datetime | None = None,
2272        content: str | None = None,
2273    ) -> list[Element]:
2274        """Return all the annotations that match the criteria.
2275
2276        Arguments:
2277
2278            creator -- str
2279
2280            start_date -- datetime instance
2281
2282            end_date --  datetime instance
2283
2284            content -- str regex
2285
2286        Return: list of Annotation
2287        """
2288        annotations = []
2289        for annotation in self._filtered_elements(
2290            "descendant::office:annotation", content=content
2291        ):
2292            if creator is not None and creator != annotation.dc_creator:
2293                continue
2294            date = annotation.dc_date
2295            if date is None:
2296                continue
2297            if start_date is not None and date < start_date:
2298                continue
2299            if end_date is not None and date >= end_date:
2300                continue
2301            annotations.append(annotation)
2302        return annotations

Return all the annotations that match the criteria.

Arguments:

creator -- str

start_date -- datetime instance

end_date --  datetime instance

content -- str regex

Return: list of Annotation

def get_annotation( self, position: int = 0, creator: str | None = None, start_date: datetime.datetime | None = None, end_date: datetime.datetime | None = None, content: str | None = None, name: str | None = None) -> Element | None:
2304    def get_annotation(
2305        self,
2306        position: int = 0,
2307        creator: str | None = None,
2308        start_date: datetime | None = None,
2309        end_date: datetime | None = None,
2310        content: str | None = None,
2311        name: str | None = None,
2312    ) -> Element | None:
2313        """Return the annotation that matches the criteria.
2314
2315        Arguments:
2316
2317            position -- int
2318
2319            creator -- str
2320
2321            start_date -- datetime instance
2322
2323            end_date -- datetime instance
2324
2325            content -- str regex
2326
2327            name -- str
2328
2329        Return: Annotation or None if not found
2330        """
2331        if name is not None:
2332            return self._filtered_element(
2333                "descendant::office:annotation", 0, office_name=name
2334            )
2335        annotations = self.get_annotations(
2336            creator=creator, start_date=start_date, end_date=end_date, content=content
2337        )
2338        if not annotations:
2339            return None
2340        try:
2341            return annotations[position]
2342        except IndexError:
2343            return None

Return the annotation that matches the criteria.

Arguments:

position -- int

creator -- str

start_date -- datetime instance

end_date -- datetime instance

content -- str regex

name -- str

Return: Annotation or None if not found

def get_annotation_ends(self) -> list[Element]:
2345    def get_annotation_ends(self) -> list[Element]:
2346        """Return all the annotation ends.
2347
2348        Return: list of Element
2349        """
2350        return self._filtered_elements("descendant::office:annotation-end")

Return all the annotation ends.

Return: list of Element

def get_annotation_end( self, position: int = 0, name: str | None = None) -> Element | None:
2352    def get_annotation_end(
2353        self,
2354        position: int = 0,
2355        name: str | None = None,
2356    ) -> Element | None:
2357        """Return the annotation end that matches the criteria.
2358
2359        Arguments:
2360
2361            position -- int
2362
2363            name -- str
2364
2365        Return: Element or None if not found
2366        """
2367        return self._filtered_element(
2368            "descendant::office:annotation-end", position, office_name=name
2369        )

Return the annotation end that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_office_names(self) -> list[str]:
2373    def get_office_names(self) -> list[str]:
2374        """Return all the used office:name tags values of the element.
2375
2376        Return: list of unique str
2377        """
2378        name_xpath_query = xpath_compile("//@office:name")
2379        response = name_xpath_query(self.__element)
2380        if not isinstance(response, list):
2381            return []
2382        return list({str(name) for name in response if name})

Return all the used office:name tags values of the element.

Return: list of unique str

def get_variable_decls(self) -> Element:
2386    def get_variable_decls(self) -> Element:
2387        """Return the container for variable declarations. Created if not
2388        found.
2389
2390        Return: Element
2391        """
2392        variable_decls = self.get_element("//text:variable-decls")
2393        if variable_decls is None:
2394            body = self.document_body
2395            if not body:
2396                raise ValueError("Empty document.body")
2397            body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD)
2398            variable_decls = body.get_element("//text:variable-decls")
2399
2400        return variable_decls  # type:ignore

Return the container for variable declarations. Created if not found.

Return: Element

def get_variable_decl_list(self) -> list[Element]:
2402    def get_variable_decl_list(self) -> list[Element]:
2403        """Return all the variable declarations.
2404
2405        Return: list of Element
2406        """
2407        return self._filtered_elements("descendant::text:variable-decl")

Return all the variable declarations.

Return: list of Element

def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2409    def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2410        """return the variable declaration for the given name.
2411
2412        Arguments:
2413
2414            name -- str
2415
2416            position -- int
2417
2418        return: Element or none if not found
2419        """
2420        return self._filtered_element(
2421            "descendant::text:variable-decl", position, text_name=name
2422        )

return the variable declaration for the given name.

Arguments:

name -- str

position -- int

return: Element or none if not found

def get_variable_sets(self, name: str | None = None) -> list[Element]:
2424    def get_variable_sets(self, name: str | None = None) -> list[Element]:
2425        """Return all the variable sets that match the criteria.
2426
2427        Arguments:
2428
2429            name -- str
2430
2431        Return: list of Element
2432        """
2433        return self._filtered_elements("descendant::text:variable-set", text_name=name)

Return all the variable sets that match the criteria.

Arguments:

name -- str

Return: list of Element

def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2435    def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2436        """Return the variable set for the given name (last one by default).
2437
2438        Arguments:
2439
2440            name -- str
2441
2442            position -- int
2443
2444        Return: Element or None if not found
2445        """
2446        return self._filtered_element(
2447            "descendant::text:variable-set", position, text_name=name
2448        )

Return the variable set for the given name (last one by default).

Arguments:

name -- str

position -- int

Return: Element or None if not found

def get_variable_set_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2450    def get_variable_set_value(
2451        self,
2452        name: str,
2453        value_type: str | None = None,
2454    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2455        """Return the last value of the given variable name.
2456
2457        Arguments:
2458
2459            name -- str
2460
2461            value_type -- 'boolean', 'currency', 'date', 'float',
2462                          'percentage', 'string', 'time' or automatic
2463
2464        Return: most appropriate Python type
2465        """
2466        variable_set = self.get_variable_set(name)
2467        if not variable_set:
2468            return None
2469        return variable_set.get_value(value_type)  # type: ignore

Return the last value of the given variable name.

Arguments:

name -- str

value_type -- 'boolean', 'currency', 'date', 'float',
              'percentage', 'string', 'time' or automatic

Return: most appropriate Python type

def get_user_field_decls(self) -> Element | None:
2473    def get_user_field_decls(self) -> Element | None:
2474        """Return the container for user field declarations. Created if not
2475        found.
2476
2477        Return: Element
2478        """
2479        user_field_decls = self.get_element("//text:user-field-decls")
2480        if user_field_decls is None:
2481            body = self.document_body
2482            if not body:
2483                raise ValueError("Empty document.body")
2484            body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD)
2485            user_field_decls = body.get_element("//text:user-field-decls")
2486
2487        return user_field_decls

Return the container for user field declarations. Created if not found.

Return: Element

def get_user_field_decl_list(self) -> list[Element]:
2489    def get_user_field_decl_list(self) -> list[Element]:
2490        """Return all the user field declarations.
2491
2492        Return: list of Element
2493        """
2494        return self._filtered_elements("descendant::text:user-field-decl")

Return all the user field declarations.

Return: list of Element

def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2496    def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2497        """return the user field declaration for the given name.
2498
2499        return: Element or none if not found
2500        """
2501        return self._filtered_element(
2502            "descendant::text:user-field-decl", position, text_name=name
2503        )

return the user field declaration for the given name.

return: Element or none if not found

def get_user_field_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2505    def get_user_field_value(
2506        self, name: str, value_type: str | None = None
2507    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2508        """Return the value of the given user field name.
2509
2510        Arguments:
2511
2512            name -- str
2513
2514            value_type -- 'boolean', 'currency', 'date', 'float',
2515                          'percentage', 'string', 'time' or automatic
2516
2517        Return: most appropriate Python type
2518        """
2519        user_field_decl = self.get_user_field_decl(name)
2520        if user_field_decl is None:
2521            return None
2522        return user_field_decl.get_value(value_type)  # type: ignore

Return the value of the given user field name.

Arguments:

name -- str

value_type -- 'boolean', 'currency', 'date', 'float',
              'percentage', 'string', 'time' or automatic

Return: most appropriate Python type

def get_user_defined_list(self) -> list[Element]:
2527    def get_user_defined_list(self) -> list[Element]:
2528        """Return all the user defined field declarations.
2529
2530        Return: list of Element
2531        """
2532        return self._filtered_elements("descendant::text:user-defined")

Return all the user defined field declarations.

Return: list of Element

def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2534    def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2535        """return the user defined declaration for the given name.
2536
2537        return: Element or none if not found
2538        """
2539        return self._filtered_element(
2540            "descendant::text:user-defined", position, text_name=name
2541        )

return the user defined declaration for the given name.

return: Element or none if not found

def get_user_defined_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2543    def get_user_defined_value(
2544        self, name: str, value_type: str | None = None
2545    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2546        """Return the value of the given user defined field name.
2547
2548        Arguments:
2549
2550            name -- str
2551
2552            value_type -- 'boolean', 'date', 'float',
2553                          'string', 'time' or automatic
2554
2555        Return: most appropriate Python type
2556        """
2557        user_defined = self.get_user_defined(name)
2558        if user_defined is None:
2559            return None
2560        return user_defined.get_value(value_type)  # type: ignore

Return the value of the given user defined field name.

Arguments:

name -- str

value_type -- 'boolean', 'date', 'float',
              'string', 'time' or automatic

Return: most appropriate Python type

def get_draw_pages( self, style: str | None = None, content: str | None = None) -> list[Element]:
2564    def get_draw_pages(
2565        self,
2566        style: str | None = None,
2567        content: str | None = None,
2568    ) -> list[Element]:
2569        """Return all the draw pages that match the criteria.
2570
2571        Arguments:
2572
2573            style -- str
2574
2575            content -- str regex
2576
2577        Return: list of DrawPage
2578        """
2579        return self._filtered_elements(
2580            "descendant::draw:page", draw_style=style, content=content
2581        )

Return all the draw pages that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of DrawPage

def get_draw_page( self, position: int = 0, name: str | None = None, content: str | None = None) -> Element | None:
2583    def get_draw_page(
2584        self,
2585        position: int = 0,
2586        name: str | None = None,
2587        content: str | None = None,
2588    ) -> Element | None:
2589        """Return the draw page that matches the criteria.
2590
2591        Arguments:
2592
2593            position -- int
2594
2595            name -- str
2596
2597            content -- str regex
2598
2599        Return: DrawPage or None if not found
2600        """
2601        return self._filtered_element(
2602            "descendant::draw:page", position, draw_name=name, content=content
2603        )

Return the draw page that matches the criteria.

Arguments:

position -- int

name -- str

content -- str regex

Return: DrawPage or None if not found

def get_bookmarks(self) -> list[Element]:
2671    def get_bookmarks(self) -> list[Element]:
2672        """Return all the bookmarks.
2673
2674        Return: list of Element
2675        """
2676        return self._filtered_elements("descendant::text:bookmark")

Return all the bookmarks.

Return: list of Element

def get_bookmark( self, position: int = 0, name: str | None = None) -> Element | None:
2678    def get_bookmark(
2679        self,
2680        position: int = 0,
2681        name: str | None = None,
2682    ) -> Element | None:
2683        """Return the bookmark that matches the criteria.
2684
2685        Arguments:
2686
2687            position -- int
2688
2689            name -- str
2690
2691        Return: Bookmark or None if not found
2692        """
2693        return self._filtered_element(
2694            "descendant::text:bookmark", position, text_name=name
2695        )

Return the bookmark that matches the criteria.

Arguments:

position -- int

name -- str

Return: Bookmark or None if not found

def get_bookmark_starts(self) -> list[Element]:
2697    def get_bookmark_starts(self) -> list[Element]:
2698        """Return all the bookmark starts.
2699
2700        Return: list of Element
2701        """
2702        return self._filtered_elements("descendant::text:bookmark-start")

Return all the bookmark starts.

Return: list of Element

def get_bookmark_start( self, position: int = 0, name: str | None = None) -> Element | None:
2704    def get_bookmark_start(
2705        self,
2706        position: int = 0,
2707        name: str | None = None,
2708    ) -> Element | None:
2709        """Return the bookmark start that matches the criteria.
2710
2711        Arguments:
2712
2713            position -- int
2714
2715            name -- str
2716
2717        Return: Element or None if not found
2718        """
2719        return self._filtered_element(
2720            "descendant::text:bookmark-start", position, text_name=name
2721        )

Return the bookmark start that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_bookmark_ends(self) -> list[Element]:
2723    def get_bookmark_ends(self) -> list[Element]:
2724        """Return all the bookmark ends.
2725
2726        Return: list of Element
2727        """
2728        return self._filtered_elements("descendant::text:bookmark-end")

Return all the bookmark ends.

Return: list of Element

def get_bookmark_end( self, position: int = 0, name: str | None = None) -> Element | None:
2730    def get_bookmark_end(
2731        self,
2732        position: int = 0,
2733        name: str | None = None,
2734    ) -> Element | None:
2735        """Return the bookmark end that matches the criteria.
2736
2737        Arguments:
2738
2739            position -- int
2740
2741            name -- str
2742
2743        Return: Element or None if not found
2744        """
2745        return self._filtered_element(
2746            "descendant::text:bookmark-end", position, text_name=name
2747        )

Return the bookmark end that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_marks_single(self) -> list[Element]:
2751    def get_reference_marks_single(self) -> list[Element]:
2752        """Return all the reference marks. Search only the tags
2753        text:reference-mark.
2754        Consider using : get_reference_marks()
2755
2756        Return: list of Element
2757        """
2758        return self._filtered_elements("descendant::text:reference-mark")

Return all the reference marks. Search only the tags text:reference-mark. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_single( self, position: int = 0, name: str | None = None) -> Element | None:
2760    def get_reference_mark_single(
2761        self,
2762        position: int = 0,
2763        name: str | None = None,
2764    ) -> Element | None:
2765        """Return the reference mark that matches the criteria. Search only the
2766        tags text:reference-mark.
2767        Consider using : get_reference_mark()
2768
2769        Arguments:
2770
2771            position -- int
2772
2773            name -- str
2774
2775        Return: Element or None if not found
2776        """
2777        return self._filtered_element(
2778            "descendant::text:reference-mark", position, text_name=name
2779        )

Return the reference mark that matches the criteria. Search only the tags text:reference-mark. Consider using : get_reference_mark()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_mark_starts(self) -> list[Element]:
2781    def get_reference_mark_starts(self) -> list[Element]:
2782        """Return all the reference mark starts. Search only the tags
2783        text:reference-mark-start.
2784        Consider using : get_reference_marks()
2785
2786        Return: list of Element
2787        """
2788        return self._filtered_elements("descendant::text:reference-mark-start")

Return all the reference mark starts. Search only the tags text:reference-mark-start. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_start( self, position: int = 0, name: str | None = None) -> Element | None:
2790    def get_reference_mark_start(
2791        self,
2792        position: int = 0,
2793        name: str | None = None,
2794    ) -> Element | None:
2795        """Return the reference mark start that matches the criteria. Search
2796        only the tags text:reference-mark-start.
2797        Consider using : get_reference_mark()
2798
2799        Arguments:
2800
2801            position -- int
2802
2803            name -- str
2804
2805        Return: Element or None if not found
2806        """
2807        return self._filtered_element(
2808            "descendant::text:reference-mark-start", position, text_name=name
2809        )

Return the reference mark start that matches the criteria. Search only the tags text:reference-mark-start. Consider using : get_reference_mark()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_mark_ends(self) -> list[Element]:
2811    def get_reference_mark_ends(self) -> list[Element]:
2812        """Return all the reference mark ends. Search only the tags
2813        text:reference-mark-end.
2814        Consider using : get_reference_marks()
2815
2816        Return: list of Element
2817        """
2818        return self._filtered_elements("descendant::text:reference-mark-end")

Return all the reference mark ends. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_end( self, position: int = 0, name: str | None = None) -> Element | None:
2820    def get_reference_mark_end(
2821        self,
2822        position: int = 0,
2823        name: str | None = None,
2824    ) -> Element | None:
2825        """Return the reference mark end that matches the criteria. Search only
2826        the tags text:reference-mark-end.
2827        Consider using : get_reference_marks()
2828
2829        Arguments:
2830
2831            position -- int
2832
2833            name -- str
2834
2835        Return: Element or None if not found
2836        """
2837        return self._filtered_element(
2838            "descendant::text:reference-mark-end", position, text_name=name
2839        )

Return the reference mark end that matches the criteria. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_marks(self) -> list[Element]:
2841    def get_reference_marks(self) -> list[Element]:
2842        """Return all the reference marks, either single position reference
2843        (text:reference-mark) or start of range reference
2844        (text:reference-mark-start).
2845
2846        Return: list of Element
2847        """
2848        return self._filtered_elements(
2849            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2850        )

Return all the reference marks, either single position reference (text:reference-mark) or start of range reference (text:reference-mark-start).

Return: list of Element

def get_reference_mark( self, position: int = 0, name: str | None = None) -> Element | None:
2852    def get_reference_mark(
2853        self,
2854        position: int = 0,
2855        name: str | None = None,
2856    ) -> Element | None:
2857        """Return the reference mark that match the criteria. Either single
2858        position reference mark (text:reference-mark) or start of range
2859        reference (text:reference-mark-start).
2860
2861        Arguments:
2862
2863            position -- int
2864
2865            name -- str
2866
2867        Return: Element or None if not found
2868        """
2869        if name:
2870            request = (
2871                f"descendant::text:reference-mark-start"
2872                f'[@text:name="{name}"] '
2873                f"| descendant::text:reference-mark"
2874                f'[@text:name="{name}"]'
2875            )
2876            return self._filtered_element(request, position=0)
2877        request = (
2878            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2879        )
2880        return self._filtered_element(request, position)

Return the reference mark that match the criteria. Either single position reference mark (text:reference-mark) or start of range reference (text:reference-mark-start).

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_references(self, name: str | None = None) -> list[Element]:
2882    def get_references(self, name: str | None = None) -> list[Element]:
2883        """Return all the references (text:reference-ref). If name is
2884        provided, returns the references of that name.
2885
2886        Return: list of Element
2887
2888        Arguments:
2889
2890            name -- str or None
2891        """
2892        if name is None:
2893            return self._filtered_elements("descendant::text:reference-ref")
2894        request = f'descendant::text:reference-ref[@text:ref-name="{name}"]'
2895        return self._filtered_elements(request)

Return all the references (text:reference-ref). If name is provided, returns the references of that name.

Return: list of Element

Arguments:

name -- str or None
def get_draw_groups( self, title: str | None = None, description: str | None = None, content: str | None = None) -> list[Element]:
2901    def get_draw_groups(
2902        self,
2903        title: str | None = None,
2904        description: str | None = None,
2905        content: str | None = None,
2906    ) -> list[Element]:
2907        return self._filtered_elements(
2908            "descendant::draw:g",
2909            svg_title=title,
2910            svg_desc=description,
2911            content=content,
2912        )
def get_draw_group( self, position: int = 0, name: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> Element | None:
2914    def get_draw_group(
2915        self,
2916        position: int = 0,
2917        name: str | None = None,
2918        title: str | None = None,
2919        description: str | None = None,
2920        content: str | None = None,
2921    ) -> Element | None:
2922        return self._filtered_element(
2923            "descendant::draw:g",
2924            position,
2925            draw_name=name,
2926            svg_title=title,
2927            svg_desc=description,
2928            content=content,
2929        )
def get_draw_lines( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
2933    def get_draw_lines(
2934        self,
2935        draw_style: str | None = None,
2936        draw_text_style: str | None = None,
2937        content: str | None = None,
2938    ) -> list[Element]:
2939        """Return all the draw lines that match the criteria.
2940
2941        Arguments:
2942
2943            draw_style -- str
2944
2945            draw_text_style -- str
2946
2947            content -- str regex
2948
2949        Return: list of odf_shape
2950        """
2951        return self._filtered_elements(
2952            "descendant::draw:line",
2953            draw_style=draw_style,
2954            draw_text_style=draw_text_style,
2955            content=content,
2956        )

Return all the draw lines that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_line( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
2958    def get_draw_line(
2959        self,
2960        position: int = 0,
2961        id: str | None = None,  # noqa:A002
2962        content: str | None = None,
2963    ) -> Element | None:
2964        """Return the draw line that matches the criteria.
2965
2966        Arguments:
2967
2968            position -- int
2969
2970            id -- str
2971
2972            content -- str regex
2973
2974        Return: odf_shape or None if not found
2975        """
2976        return self._filtered_element(
2977            "descendant::draw:line", position, draw_id=id, content=content
2978        )

Return the draw line that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_rectangles( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
2982    def get_draw_rectangles(
2983        self,
2984        draw_style: str | None = None,
2985        draw_text_style: str | None = None,
2986        content: str | None = None,
2987    ) -> list[Element]:
2988        """Return all the draw rectangles that match the criteria.
2989
2990        Arguments:
2991
2992            draw_style -- str
2993
2994            draw_text_style -- str
2995
2996            content -- str regex
2997
2998        Return: list of odf_shape
2999        """
3000        return self._filtered_elements(
3001            "descendant::draw:rect",
3002            draw_style=draw_style,
3003            draw_text_style=draw_text_style,
3004            content=content,
3005        )

Return all the draw rectangles that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_rectangle( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3007    def get_draw_rectangle(
3008        self,
3009        position: int = 0,
3010        id: str | None = None,  # noqa:A002
3011        content: str | None = None,
3012    ) -> Element | None:
3013        """Return the draw rectangle that matches the criteria.
3014
3015        Arguments:
3016
3017            position -- int
3018
3019            id -- str
3020
3021            content -- str regex
3022
3023        Return: odf_shape or None if not found
3024        """
3025        return self._filtered_element(
3026            "descendant::draw:rect", position, draw_id=id, content=content
3027        )

Return the draw rectangle that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_ellipses( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
3031    def get_draw_ellipses(
3032        self,
3033        draw_style: str | None = None,
3034        draw_text_style: str | None = None,
3035        content: str | None = None,
3036    ) -> list[Element]:
3037        """Return all the draw ellipses that match the criteria.
3038
3039        Arguments:
3040
3041            draw_style -- str
3042
3043            draw_text_style -- str
3044
3045            content -- str regex
3046
3047        Return: list of odf_shape
3048        """
3049        return self._filtered_elements(
3050            "descendant::draw:ellipse",
3051            draw_style=draw_style,
3052            draw_text_style=draw_text_style,
3053            content=content,
3054        )

Return all the draw ellipses that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_ellipse( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3056    def get_draw_ellipse(
3057        self,
3058        position: int = 0,
3059        id: str | None = None,  # noqa:A002
3060        content: str | None = None,
3061    ) -> Element | None:
3062        """Return the draw ellipse that matches the criteria.
3063
3064        Arguments:
3065
3066            position -- int
3067
3068            id -- str
3069
3070            content -- str regex
3071
3072        Return: odf_shape or None if not found
3073        """
3074        return self._filtered_element(
3075            "descendant::draw:ellipse", position, draw_id=id, content=content
3076        )

Return the draw ellipse that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_connectors( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
3080    def get_draw_connectors(
3081        self,
3082        draw_style: str | None = None,
3083        draw_text_style: str | None = None,
3084        content: str | None = None,
3085    ) -> list[Element]:
3086        """Return all the draw connectors that match the criteria.
3087
3088        Arguments:
3089
3090            draw_style -- str
3091
3092            draw_text_style -- str
3093
3094            content -- str regex
3095
3096        Return: list of odf_shape
3097        """
3098        return self._filtered_elements(
3099            "descendant::draw:connector",
3100            draw_style=draw_style,
3101            draw_text_style=draw_text_style,
3102            content=content,
3103        )

Return all the draw connectors that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_connector( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3105    def get_draw_connector(
3106        self,
3107        position: int = 0,
3108        id: str | None = None,  # noqa:A002
3109        content: str | None = None,
3110    ) -> Element | None:
3111        """Return the draw connector that matches the criteria.
3112
3113        Arguments:
3114
3115            position -- int
3116
3117            id -- str
3118
3119            content -- str regex
3120
3121        Return: odf_shape or None if not found
3122        """
3123        return self._filtered_element(
3124            "descendant::draw:connector", position, draw_id=id, content=content
3125        )

Return the draw connector that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_orphan_draw_connectors(self) -> list[Element]:
3127    def get_orphan_draw_connectors(self) -> list[Element]:
3128        """Return a list of connectors which don't have any shape connected
3129        to them.
3130        """
3131        connectors = []
3132        for connector in self.get_draw_connectors():
3133            start_shape = connector.get_attribute("draw:start-shape")
3134            end_shape = connector.get_attribute("draw:end-shape")
3135            if start_shape is None and end_shape is None:
3136                connectors.append(connector)
3137        return connectors

Return a list of connectors which don't have any shape connected to them.

def get_tracked_changes(self) -> Element | None:
3141    def get_tracked_changes(self) -> Element | None:
3142        """Return the tracked-changes part in the text body."""
3143        return self.get_element("//text:tracked-changes")

Return the tracked-changes part in the text body.

def get_changes_ids(self) -> list[Element | Text]:
3145    def get_changes_ids(self) -> list[Element | Text]:
3146        """Return a list of ids that refers to a change region in the tracked
3147        changes list.
3148        """
3149        # Insertion changes
3150        xpath_query = "descendant::text:change-start/@text:change-id"
3151        # Deletion changes
3152        xpath_query += " | descendant::text:change/@text:change-id"
3153        return self.xpath(xpath_query)

Return a list of ids that refers to a change region in the tracked changes list.

def get_text_change_deletions(self) -> list[Element]:
3155    def get_text_change_deletions(self) -> list[Element]:
3156        """Return all the text changes of deletion kind: the tags text:change.
3157        Consider using : get_text_changes()
3158
3159        Return: list of Element
3160        """
3161        return self._filtered_elements("descendant::text:text:change")

Return all the text changes of deletion kind: the tags text:change. Consider using : get_text_changes()

Return: list of Element

def get_text_change_deletion( self, position: int = 0, idx: str | None = None) -> Element | None:
3163    def get_text_change_deletion(
3164        self,
3165        position: int = 0,
3166        idx: str | None = None,
3167    ) -> Element | None:
3168        """Return the text change of deletion kind that matches the criteria.
3169        Search only for the tags text:change.
3170        Consider using : get_text_change()
3171
3172        Arguments:
3173
3174            position -- int
3175
3176            idx -- str
3177
3178        Return: Element or None if not found
3179        """
3180        return self._filtered_element(
3181            "descendant::text:change", position, change_id=idx
3182        )

Return the text change of deletion kind that matches the criteria. Search only for the tags text:change. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_change_starts(self) -> list[Element]:
3184    def get_text_change_starts(self) -> list[Element]:
3185        """Return all the text change-start. Search only for the tags
3186        text:change-start.
3187        Consider using : get_text_changes()
3188
3189        Return: list of Element
3190        """
3191        return self._filtered_elements("descendant::text:change-start")

Return all the text change-start. Search only for the tags text:change-start. Consider using : get_text_changes()

Return: list of Element

def get_text_change_start( self, position: int = 0, idx: str | None = None) -> Element | None:
3193    def get_text_change_start(
3194        self,
3195        position: int = 0,
3196        idx: str | None = None,
3197    ) -> Element | None:
3198        """Return the text change-start that matches the criteria. Search
3199        only the tags text:change-start.
3200        Consider using : get_text_change()
3201
3202        Arguments:
3203
3204            position -- int
3205
3206            idx -- str
3207
3208        Return: Element or None if not found
3209        """
3210        return self._filtered_element(
3211            "descendant::text:change-start", position, change_id=idx
3212        )

Return the text change-start that matches the criteria. Search only the tags text:change-start. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_change_ends(self) -> list[Element]:
3214    def get_text_change_ends(self) -> list[Element]:
3215        """Return all the text change-end. Search only the tags
3216        text:change-end.
3217        Consider using : get_text_changes()
3218
3219        Return: list of Element
3220        """
3221        return self._filtered_elements("descendant::text:change-end")

Return all the text change-end. Search only the tags text:change-end. Consider using : get_text_changes()

Return: list of Element

def get_text_change_end( self, position: int = 0, idx: str | None = None) -> Element | None:
3223    def get_text_change_end(
3224        self,
3225        position: int = 0,
3226        idx: str | None = None,
3227    ) -> Element | None:
3228        """Return the text change-end that matches the criteria. Search only
3229        the tags text:change-end.
3230        Consider using : get_text_change()
3231
3232        Arguments:
3233
3234            position -- int
3235
3236            idx -- str
3237
3238        Return: Element or None if not found
3239        """
3240        return self._filtered_element(
3241            "descendant::text:change-end", position, change_id=idx
3242        )

Return the text change-end that matches the criteria. Search only the tags text:change-end. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_changes(self) -> list[Element]:
3244    def get_text_changes(self) -> list[Element]:
3245        """Return all the text changes, either single deletion
3246        (text:change) or start of range of changes (text:change-start).
3247
3248        Return: list of Element
3249        """
3250        request = "descendant::text:change-start | descendant::text:change"
3251        return self._filtered_elements(request)

Return all the text changes, either single deletion (text:change) or start of range of changes (text:change-start).

Return: list of Element

def get_text_change( self, position: int = 0, idx: str | None = None) -> Element | None:
3253    def get_text_change(
3254        self,
3255        position: int = 0,
3256        idx: str | None = None,
3257    ) -> Element | None:
3258        """Return the text change that matches the criteria. Either single
3259        deletion (text:change) or start of range of changes (text:change-start).
3260        position : index of the element to retrieve if several matches, default
3261        is 0.
3262        idx : change-id of the element.
3263
3264        Arguments:
3265
3266            position -- int
3267
3268            idx -- str
3269
3270        Return: Element or None if not found
3271        """
3272        if idx:
3273            request = (
3274                f'descendant::text:change-start[@text:change-id="{idx}"] '
3275                f'| descendant::text:change[@text:change-id="{idx}"]'
3276            )
3277            return self._filtered_element(request, 0)
3278        request = "descendant::text:change-start | descendant::text:change"
3279        return self._filtered_element(request, position)

Return the text change that matches the criteria. Either single deletion (text:change) or start of range of changes (text:change-start). position : index of the element to retrieve if several matches, default is 0. idx : change-id of the element.

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_tocs(self) -> list[Element]:
3283    def get_tocs(self) -> list[Element]:
3284        """Return all the tables of contents.
3285
3286        Return: list of odf_toc
3287        """
3288        return self._filtered_elements("text:table-of-content")

Return all the tables of contents.

Return: list of odf_toc

def get_toc( self, position: int = 0, content: str | None = None) -> Element | None:
3290    def get_toc(
3291        self,
3292        position: int = 0,
3293        content: str | None = None,
3294    ) -> Element | None:
3295        """Return the table of contents that matches the criteria.
3296
3297        Arguments:
3298
3299            position -- int
3300
3301            content -- str regex
3302
3303        Return: odf_toc or None if not found
3304        """
3305        return self._filtered_element(
3306            "text:table-of-content", position, content=content
3307        )

Return the table of contents that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: odf_toc or None if not found

def get_styles(self, family: str | None = None) -> list[Element]:
3329    def get_styles(self, family: str | None = None) -> list[Element]:
3330        # Both common and default styles
3331        tagname = self._get_style_tagname(family)
3332        return self._filtered_elements(tagname, family=family)
def get_style( self, family: str, name_or_element: str | Element | None = None, display_name: str | None = None) -> Element | None:
3334    def get_style(
3335        self,
3336        family: str,
3337        name_or_element: str | Element | None = None,
3338        display_name: str | None = None,
3339    ) -> Element | None:
3340        """Return the style uniquely identified by the family/name pair. If
3341        the argument is already a style object, it will return it.
3342
3343        If the name is not the internal name but the name you gave in the
3344        desktop application, use display_name instead.
3345
3346        Arguments:
3347
3348            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
3349                      'number'
3350
3351            name_or_element -- str or Style
3352
3353            display_name -- str
3354
3355        Return: odf_style or None if not found
3356        """
3357        if isinstance(name_or_element, Element):
3358            name = self.get_attribute("style:name")
3359            if name is not None:
3360                return name_or_element
3361            else:
3362                raise ValueError(f"Not a odf_style ? {name_or_element!r}")
3363        style_name = name_or_element
3364        is_default = not (style_name or display_name)
3365        tagname = self._get_style_tagname(family, is_default=is_default)
3366        # famattr became None if no "style:family" attribute
3367        if family:
3368            return self._filtered_element(
3369                tagname,
3370                0,
3371                style_name=style_name,
3372                display_name=display_name,
3373                family=family,
3374            )
3375        else:
3376            return self._filtered_element(
3377                tagname,
3378                0,
3379                draw_name=style_name or display_name,
3380                family=family,
3381            )

Return the style uniquely identified by the family/name pair. If the argument is already a style object, it will return it.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text', 'graphic', 'table', 'list',
          'number'

name_or_element -- str or Style

display_name -- str

Return: odf_style or None if not found

class ElementTyped(odfdo.Element):
 35class ElementTyped(Element):
 36    def set_value_and_type(  # noqa: C901
 37        self,
 38        value: Any,
 39        value_type: str | None = None,
 40        text: str | None = None,
 41        currency: str | None = None,
 42    ) -> str | None:
 43        # Remove possible previous value and type
 44        for name in (
 45            "office:value-type",
 46            "office:boolean-value",
 47            "office:value",
 48            "office:date-value",
 49            "office:string-value",
 50            "office:time-value",
 51            "table:formula",
 52            "office:currency",
 53            "calcext:value-type",
 54            "loext:value-type",
 55        ):
 56            with contextlib.suppress(KeyError):
 57                self.del_attribute(name)
 58        if isinstance(value, bytes):
 59            value = bytes_to_str(value)
 60        if isinstance(value_type, bytes):
 61            value_type = bytes_to_str(value_type)
 62        if isinstance(text, bytes):
 63            text = bytes_to_str(text)
 64        if isinstance(currency, bytes):
 65            currency = bytes_to_str(currency)
 66        if value is None:
 67            self._erase_text_content()
 68            return text
 69        if isinstance(value, bool):
 70            if value_type is None:
 71                value_type = "boolean"
 72            if text is None:
 73                text = "true" if value else "false"
 74            value = Boolean.encode(value)
 75        elif isinstance(value, (int, float, Decimal)):
 76            if value_type == "percentage":
 77                text = "%d %%" % int(value * 100)
 78            if value_type is None:
 79                value_type = "float"
 80            if text is None:
 81                text = str(value)
 82            value = str(value)
 83        elif isinstance(value, datetime):
 84            if value_type is None:
 85                value_type = "date"
 86            if text is None:
 87                text = str(DateTime.encode(value))
 88            value = DateTime.encode(value)
 89        elif isinstance(value, date):
 90            if value_type is None:
 91                value_type = "date"
 92            if text is None:
 93                text = str(Date.encode(value))
 94            value = Date.encode(value)
 95        elif isinstance(value, str):
 96            if value_type is None:
 97                value_type = "string"
 98            if text is None:
 99                text = value
100        elif isinstance(value, timedelta):
101            if value_type is None:
102                value_type = "time"
103            if text is None:
104                text = str(Duration.encode(value))
105            value = Duration.encode(value)
106        elif value is not None:
107            raise TypeError(f"Type unknown: '{value!r}'")
108
109        if value_type is not None:
110            self.set_attribute("office:value-type", value_type)
111            self.set_attribute("calcext:value-type", value_type)
112        if value_type == "boolean":
113            self.set_attribute("office:boolean-value", value)
114        elif value_type == "currency":
115            self.set_attribute("office:value", value)
116            self.set_attribute("office:currency", currency)
117        elif value_type == "date":
118            self.set_attribute("office:date-value", value)
119        elif value_type in ("float", "percentage"):
120            self.set_attribute("office:value", value)
121            self.set_attribute("calcext:value", value)
122        elif value_type == "string":
123            self.set_attribute("office:string-value", value)
124        elif value_type == "time":
125            self.set_attribute("office:time-value", value)
126
127        return text
128
129    def _get_typed_value(  # noqa: C901
130        self,
131        value_type: str | None = None,
132        try_get_text: bool = True,
133    ) -> tuple[Any, str | None]:
134        """Return Python typed value.
135
136        Only for "with office:value-type" elements, not for meta fields."""
137        value: Decimal | str | bool | None = None
138        if value_type is None:
139            read_value_type = self.get_attribute("office:value-type")
140            if isinstance(read_value_type, bool):
141                raise TypeError(
142                    f'Wrong type for "office:value-type": {type(read_value_type)}'
143                )
144            value_type = read_value_type
145        # value_type = to_str(value_type)
146        if value_type == "boolean":
147            value = self.get_attribute("office:boolean-value")
148            return (value, value_type)
149        if value_type in {"float", "percentage", "currency"}:
150            read_number = self.get_attribute("office:value")
151            if not isinstance(read_number, (Decimal, str)):
152                raise TypeError(f'Wrong type for "office:value": {type(read_number)}')
153            value = Decimal(read_number)
154            # Return 3 instead of 3.0 if possible
155            if int(value) == value:
156                return (int(value), value_type)
157            return (value, value_type)
158        if value_type == "date":
159            read_attribute = self.get_attribute("office:date-value")
160            if not isinstance(read_attribute, str):
161                raise TypeError(
162                    f'Wrong type for "office:date-value": {type(read_attribute)}'
163                )
164            if "T" in read_attribute:
165                return (DateTime.decode(read_attribute), value_type)
166            return (Date.decode(read_attribute), value_type)
167        if value_type == "string":
168            value = self.get_attribute("office:string-value")
169            if value is not None:
170                return (str(value), value_type)
171            if try_get_text:
172                list_value = [
173                    para.text_recursive for para in self.get_elements("text:p")
174                ]
175                if list_value:
176                    return ("\n".join(list_value), value_type)
177            return (None, value_type)
178        if value_type == "time":
179            read_value = self.get_attribute("office:time-value")
180            if not isinstance(read_value, str):
181                raise TypeError(
182                    f'Wrong type for "office:time-value": {type(read_value)}'
183                )
184            time_value = Duration.decode(read_value)
185            return (time_value, value_type)
186        if value_type is None:
187            return (None, None)
188        raise ValueError(f'Unexpected value type: "{value_type}"')
189
190    def get_value(
191        self,
192        value_type: str | None = None,
193        try_get_text: bool = True,
194        get_type: bool = False,
195    ) -> Any | tuple[Any, str]:
196        """Return Python typed value.
197
198        Only for "with office:value-type" elements, not for meta fields."""
199        if get_type:
200            return self._get_typed_value(
201                value_type=value_type,
202                try_get_text=try_get_text,
203            )
204        return self._get_typed_value(
205            value_type=value_type,
206            try_get_text=try_get_text,
207        )[0]

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

def set_value_and_type( self, value: Any, value_type: str | None = None, text: str | None = None, currency: str | None = None) -> str | None:
 36    def set_value_and_type(  # noqa: C901
 37        self,
 38        value: Any,
 39        value_type: str | None = None,
 40        text: str | None = None,
 41        currency: str | None = None,
 42    ) -> str | None:
 43        # Remove possible previous value and type
 44        for name in (
 45            "office:value-type",
 46            "office:boolean-value",
 47            "office:value",
 48            "office:date-value",
 49            "office:string-value",
 50            "office:time-value",
 51            "table:formula",
 52            "office:currency",
 53            "calcext:value-type",
 54            "loext:value-type",
 55        ):
 56            with contextlib.suppress(KeyError):
 57                self.del_attribute(name)
 58        if isinstance(value, bytes):
 59            value = bytes_to_str(value)
 60        if isinstance(value_type, bytes):
 61            value_type = bytes_to_str(value_type)
 62        if isinstance(text, bytes):
 63            text = bytes_to_str(text)
 64        if isinstance(currency, bytes):
 65            currency = bytes_to_str(currency)
 66        if value is None:
 67            self._erase_text_content()
 68            return text
 69        if isinstance(value, bool):
 70            if value_type is None:
 71                value_type = "boolean"
 72            if text is None:
 73                text = "true" if value else "false"
 74            value = Boolean.encode(value)
 75        elif isinstance(value, (int, float, Decimal)):
 76            if value_type == "percentage":
 77                text = "%d %%" % int(value * 100)
 78            if value_type is None:
 79                value_type = "float"
 80            if text is None:
 81                text = str(value)
 82            value = str(value)
 83        elif isinstance(value, datetime):
 84            if value_type is None:
 85                value_type = "date"
 86            if text is None:
 87                text = str(DateTime.encode(value))
 88            value = DateTime.encode(value)
 89        elif isinstance(value, date):
 90            if value_type is None:
 91                value_type = "date"
 92            if text is None:
 93                text = str(Date.encode(value))
 94            value = Date.encode(value)
 95        elif isinstance(value, str):
 96            if value_type is None:
 97                value_type = "string"
 98            if text is None:
 99                text = value
100        elif isinstance(value, timedelta):
101            if value_type is None:
102                value_type = "time"
103            if text is None:
104                text = str(Duration.encode(value))
105            value = Duration.encode(value)
106        elif value is not None:
107            raise TypeError(f"Type unknown: '{value!r}'")
108
109        if value_type is not None:
110            self.set_attribute("office:value-type", value_type)
111            self.set_attribute("calcext:value-type", value_type)
112        if value_type == "boolean":
113            self.set_attribute("office:boolean-value", value)
114        elif value_type == "currency":
115            self.set_attribute("office:value", value)
116            self.set_attribute("office:currency", currency)
117        elif value_type == "date":
118            self.set_attribute("office:date-value", value)
119        elif value_type in ("float", "percentage"):
120            self.set_attribute("office:value", value)
121            self.set_attribute("calcext:value", value)
122        elif value_type == "string":
123            self.set_attribute("office:string-value", value)
124        elif value_type == "time":
125            self.set_attribute("office:time-value", value)
126
127        return text
def get_value( self, value_type: str | None = None, try_get_text: bool = True, get_type: bool = False) -> typing.Any | tuple[typing.Any, str]:
190    def get_value(
191        self,
192        value_type: str | None = None,
193        try_get_text: bool = True,
194        get_type: bool = False,
195    ) -> Any | tuple[Any, str]:
196        """Return Python typed value.
197
198        Only for "with office:value-type" elements, not for meta fields."""
199        if get_type:
200            return self._get_typed_value(
201                value_type=value_type,
202                try_get_text=try_get_text,
203            )
204        return self._get_typed_value(
205            value_type=value_type,
206            try_get_text=try_get_text,
207        )[0]

Return Python typed value.

Only for "with office:value-type" elements, not for meta fields.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class EllipseShape(odfdo.shapes.ShapeBase):
194class EllipseShape(ShapeBase):
195    """Create a ellipse shape.
196
197    Arguments:
198
199        style -- str
200
201        text_style -- str
202
203        draw_id -- str
204
205        layer -- str
206
207        position -- (str, str)
208
209        size -- (str, str)
210
211    """
212
213    _tag = "draw:ellipse"
214    _properties: tuple[PropDef, ...] = ()
215
216    def __init__(
217        self,
218        style: str | None = None,
219        text_style: str | None = None,
220        draw_id: str | None = None,
221        layer: str | None = None,
222        position: tuple | None = None,
223        size: tuple | None = None,
224        **kwargs: Any,
225    ) -> None:
226        kwargs.update(
227            {
228                "style": style,
229                "text_style": text_style,
230                "draw_id": draw_id,
231                "layer": layer,
232                "size": size,
233                "position": position,
234            }
235        )
236        super().__init__(**kwargs)

Create a ellipse shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

position -- (str, str)

size -- (str, str)
EllipseShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, position: tuple | None = None, size: tuple | None = None, **kwargs: Any)
216    def __init__(
217        self,
218        style: str | None = None,
219        text_style: str | None = None,
220        draw_id: str | None = None,
221        layer: str | None = None,
222        position: tuple | None = None,
223        size: tuple | None = None,
224        **kwargs: Any,
225    ) -> None:
226        kwargs.update(
227            {
228                "style": style,
229                "text_style": text_style,
230                "draw_id": draw_id,
231                "layer": layer,
232                "size": size,
233                "position": position,
234            }
235        )
236        super().__init__(**kwargs)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
FIRST_CHILD = 0
class Frame(odfdo.Element, odfdo.frame.AnchorMix, odfdo.frame.PosMix, odfdo.frame.ZMix, odfdo.frame.SizeMix):
168class Frame(Element, AnchorMix, PosMix, ZMix, SizeMix):
169    """ODF Frame "draw:frame"
170
171    Frames are not useful by themselves. You should consider calling
172    Frame.image_frame() or Frame.text_frame directly.
173    """
174
175    _tag = "draw:frame"
176    _properties = (
177        PropDef("name", "draw:name"),
178        PropDef("draw_id", "draw:id"),
179        PropDef("width", "svg:width"),
180        PropDef("height", "svg:height"),
181        PropDef("style", "draw:style-name"),
182        PropDef("pos_x", "svg:x"),
183        PropDef("pos_y", "svg:y"),
184        PropDef("presentation_class", "presentation:class"),
185        PropDef("layer", "draw:layer"),
186        PropDef("presentation_style", "presentation:style-name"),
187    )
188
189    def __init__(  # noqa:  C901
190        self,
191        name: str | None = None,
192        draw_id: str | None = None,
193        style: str | None = None,
194        position: tuple | None = None,
195        size: tuple = ("1cm", "1cm"),
196        z_index: int = 0,
197        presentation_class: str | None = None,
198        anchor_type: str | None = None,
199        anchor_page: int | None = None,
200        layer: str | None = None,
201        presentation_style: str | None = None,
202        **kwargs: Any,
203    ) -> None:
204        """Create a frame element of the given size. Position is relative to the
205        context the frame is inserted in. If positioned by page, give the page
206        number and the x, y position.
207
208        Size is a (width, height) tuple and position is a (left, top) tuple; items
209        are strings including the unit, e.g. ('10cm', '15cm').
210
211        Frames are not useful by themselves. You should consider calling:
212            Frame.image_frame()
213        or
214            Frame.text_frame()
215
216
217        Arguments:
218
219            name -- str
220
221            draw_id -- str
222
223            style -- str
224
225            position -- (str, str)
226
227            size -- (str, str)
228
229            z_index -- int (default 0)
230
231            presentation_class -- str
232
233            anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
234
235            anchor_page -- int, page number is anchor_type is 'page'
236
237            layer -- str
238
239            presentation_style -- str
240        """
241        super().__init__(**kwargs)
242        if self._do_init:
243            self.size = size
244            self.z_index = z_index
245            if name:
246                self.name = name
247            if draw_id is not None:
248                self.draw_id = draw_id
249            if style is not None:
250                self.style = style
251            if position is not None:
252                self.position = position
253            if presentation_class is not None:
254                self.presentation_class = presentation_class
255            if anchor_type:
256                self.anchor_type = anchor_type
257            if position and not anchor_type:
258                self.anchor_type = "paragraph"
259            if anchor_page is not None:
260                self.anchor_page = anchor_page
261            if layer is not None:
262                self.layer = layer
263            if presentation_style is not None:
264                self.presentation_style = presentation_style
265
266    @classmethod
267    def image_frame(
268        cls,
269        image: Element | str,
270        text: str | None = None,
271        name: str | None = None,
272        draw_id: str | None = None,
273        style: str | None = None,
274        position: tuple | None = None,
275        size: tuple = ("1cm", "1cm"),
276        z_index: int = 0,
277        presentation_class: str | None = None,
278        anchor_type: str | None = None,
279        anchor_page: int | None = None,
280        layer: str | None = None,
281        presentation_style: str | None = None,
282        **kwargs: Any,
283    ) -> Element:
284        """Create a ready-to-use image, since image must be embedded in a
285        frame.
286
287        The optionnal text will appear above the image.
288
289        Arguments:
290
291            image -- DrawImage or str, DrawImage element or URL of the image
292
293            text -- str, text for the image
294
295            See Frame() initialization for the other arguments
296
297        Return: Frame
298        """
299        frame = cls(
300            name=name,
301            draw_id=draw_id,
302            style=style,
303            position=position,
304            size=size,
305            z_index=z_index,
306            presentation_class=presentation_class,
307            anchor_type=anchor_type,
308            anchor_page=anchor_page,
309            layer=layer,
310            presentation_style=presentation_style,
311            **kwargs,
312        )
313        image_element = frame.set_image(image)
314        if text:
315            image_element.text_content = text
316        return frame
317
318    @classmethod
319    def text_frame(
320        cls,
321        text_or_element: Iterable[Element] | Element | str,
322        text_style: str | None = None,
323        name: str | None = None,
324        draw_id: str | None = None,
325        style: str | None = None,
326        position: tuple | None = None,
327        size: tuple = ("1cm", "1cm"),
328        z_index: int = 0,
329        presentation_class: str | None = None,
330        anchor_type: str | None = None,
331        anchor_page: int | None = None,
332        layer: str | None = None,
333        presentation_style: str | None = None,
334        **kwargs: Any,
335    ) -> Element:
336        """Create a ready-to-use text box, since text box must be embedded in
337        a frame.
338
339        The optionnal text will appear above the image.
340
341        Arguments:
342
343            text_or_element -- str or Element, or list of them, text content
344                               of the text box.
345
346            text_style -- str, name of the style for the text
347
348            See Frame() initialization for the other arguments
349
350        Return: Frame
351        """
352        frame = cls(
353            name=name,
354            draw_id=draw_id,
355            style=style,
356            position=position,
357            size=size,
358            z_index=z_index,
359            presentation_class=presentation_class,
360            anchor_type=anchor_type,
361            anchor_page=anchor_page,
362            layer=layer,
363            presentation_style=presentation_style,
364            **kwargs,
365        )
366        frame.set_text_box(text_or_element, text_style)
367        return frame
368
369    @property
370    def text_content(self) -> str:
371        text_box = self.get_element("draw:text-box")
372        if text_box is None:
373            return ""
374        return text_box.text_content
375
376    @text_content.setter
377    def text_content(self, text_or_element: Element | str) -> None:
378        text_box = self.get_element("draw:text-box")
379        if text_box is None:
380            text_box = Element.from_tag("draw:text-box")
381            self.append(text_box)
382        if isinstance(text_or_element, Element):
383            text_box.clear()
384            text_box.append(text_or_element)
385        else:
386            text_box.text_content = text_or_element
387
388    def get_image(
389        self,
390        position: int = 0,
391        name: str | None = None,
392        url: str | None = None,
393        content: str | None = None,
394    ) -> Element | None:
395        return self.get_element("draw:image")
396
397    def set_image(self, url_or_element: Element | str) -> Element:
398        image = self.get_image()
399        if image is None:
400            if isinstance(url_or_element, Element):
401                image = url_or_element
402                self.append(image)
403            else:
404                image = DrawImage(url_or_element)
405                self.append(image)
406        else:
407            if isinstance(url_or_element, Element):
408                image.delete()
409                image = url_or_element
410                self.append(image)
411            else:
412                image.set_url(url_or_element)  # type: ignore
413        return image
414
415    def get_text_box(self) -> Element | None:
416        return self.get_element("draw:text-box")
417
418    def set_text_box(
419        self,
420        text_or_element: Iterable[Element | str] | Element | str,
421        text_style: str | None = None,
422    ) -> Element:
423        text_box = self.get_text_box()
424        if text_box is None:
425            text_box = Element.from_tag("draw:text-box")
426            self.append(text_box)
427        else:
428            text_box.clear()
429        if isinstance(text_or_element, (Element, str)):
430            text_or_element_list: Iterable[Element | str] = [text_or_element]
431        else:
432            text_or_element_list = text_or_element
433        for item in text_or_element_list:
434            if isinstance(item, str):
435                text_box.append(Paragraph(item, style=text_style))
436            else:
437                text_box.append(item)
438        return text_box
439
440    @staticmethod
441    def _get_formatted_text_subresult(context: dict, element: Element) -> str:
442        str_list = ["  "]
443        for child in element.children:
444            str_list.append(child.get_formatted_text(context))
445        subresult = "".join(str_list)
446        subresult = subresult.replace("\n", "\n  ")
447        return subresult.rstrip(" ")
448
449    def get_formatted_text(  # noqa:  C901
450        self,
451        context: dict | None = None,
452    ) -> str:
453        if not context:
454            context = {}
455        result = []
456        for element in self.children:
457            tag = element.tag
458            if tag == "draw:image":
459                if context["rst_mode"]:
460                    filename = element.get_attribute("xlink:href")
461
462                    # Compute width and height
463                    width, height = self.size
464                    if width is not None:
465                        width = Unit(width)
466                        width = width.convert("px", DPI)
467                    if height is not None:
468                        height = Unit(height)
469                        height = height.convert("px", DPI)
470
471                    # Insert or not ?
472                    if context["no_img_level"]:
473                        context["img_counter"] += 1
474                        ref = f"|img{context['img_counter']}|"
475                        result.append(ref)
476                        context["images"].append((ref, filename, (width, height)))
477                    else:
478                        result.append(f"\n.. image:: {filename}\n")
479                        if width is not None:
480                            result.append(f"   :width: {width}\n")
481                        if height is not None:
482                            result.append(f"   :height: {height}\n")
483                else:
484                    result.append(f"[Image {element.get_attribute('xlink:href')}]\n")
485            elif tag == "draw:text-box":
486                result.append(self._get_formatted_text_subresult(context, element))
487            else:
488                result.append(element.get_formatted_text(context))
489        result.append("\n")
490        return "".join(result)

ODF Frame "draw:frame"

Frames are not useful by themselves. You should consider calling Frame.image_frame() or Frame.text_frame directly.

Frame( name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any)
189    def __init__(  # noqa:  C901
190        self,
191        name: str | None = None,
192        draw_id: str | None = None,
193        style: str | None = None,
194        position: tuple | None = None,
195        size: tuple = ("1cm", "1cm"),
196        z_index: int = 0,
197        presentation_class: str | None = None,
198        anchor_type: str | None = None,
199        anchor_page: int | None = None,
200        layer: str | None = None,
201        presentation_style: str | None = None,
202        **kwargs: Any,
203    ) -> None:
204        """Create a frame element of the given size. Position is relative to the
205        context the frame is inserted in. If positioned by page, give the page
206        number and the x, y position.
207
208        Size is a (width, height) tuple and position is a (left, top) tuple; items
209        are strings including the unit, e.g. ('10cm', '15cm').
210
211        Frames are not useful by themselves. You should consider calling:
212            Frame.image_frame()
213        or
214            Frame.text_frame()
215
216
217        Arguments:
218
219            name -- str
220
221            draw_id -- str
222
223            style -- str
224
225            position -- (str, str)
226
227            size -- (str, str)
228
229            z_index -- int (default 0)
230
231            presentation_class -- str
232
233            anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
234
235            anchor_page -- int, page number is anchor_type is 'page'
236
237            layer -- str
238
239            presentation_style -- str
240        """
241        super().__init__(**kwargs)
242        if self._do_init:
243            self.size = size
244            self.z_index = z_index
245            if name:
246                self.name = name
247            if draw_id is not None:
248                self.draw_id = draw_id
249            if style is not None:
250                self.style = style
251            if position is not None:
252                self.position = position
253            if presentation_class is not None:
254                self.presentation_class = presentation_class
255            if anchor_type:
256                self.anchor_type = anchor_type
257            if position and not anchor_type:
258                self.anchor_type = "paragraph"
259            if anchor_page is not None:
260                self.anchor_page = anchor_page
261            if layer is not None:
262                self.layer = layer
263            if presentation_style is not None:
264                self.presentation_style = presentation_style

Create a frame element of the given size. Position is relative to the context the frame is inserted in. If positioned by page, give the page number and the x, y position.

Size is a (width, height) tuple and position is a (left, top) tuple; items are strings including the unit, e.g. ('10cm', '15cm').

Frames are not useful by themselves. You should consider calling: Frame.image_frame() or Frame.text_frame()

Arguments:

name -- str

draw_id -- str

style -- str

position -- (str, str)

size -- (str, str)

z_index -- int (default 0)

presentation_class -- str

anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'

anchor_page -- int, page number is anchor_type is 'page'

layer -- str

presentation_style -- str
@classmethod
def image_frame( cls, image: Element | str, text: str | None = None, name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any) -> Element:
266    @classmethod
267    def image_frame(
268        cls,
269        image: Element | str,
270        text: str | None = None,
271        name: str | None = None,
272        draw_id: str | None = None,
273        style: str | None = None,
274        position: tuple | None = None,
275        size: tuple = ("1cm", "1cm"),
276        z_index: int = 0,
277        presentation_class: str | None = None,
278        anchor_type: str | None = None,
279        anchor_page: int | None = None,
280        layer: str | None = None,
281        presentation_style: str | None = None,
282        **kwargs: Any,
283    ) -> Element:
284        """Create a ready-to-use image, since image must be embedded in a
285        frame.
286
287        The optionnal text will appear above the image.
288
289        Arguments:
290
291            image -- DrawImage or str, DrawImage element or URL of the image
292
293            text -- str, text for the image
294
295            See Frame() initialization for the other arguments
296
297        Return: Frame
298        """
299        frame = cls(
300            name=name,
301            draw_id=draw_id,
302            style=style,
303            position=position,
304            size=size,
305            z_index=z_index,
306            presentation_class=presentation_class,
307            anchor_type=anchor_type,
308            anchor_page=anchor_page,
309            layer=layer,
310            presentation_style=presentation_style,
311            **kwargs,
312        )
313        image_element = frame.set_image(image)
314        if text:
315            image_element.text_content = text
316        return frame

Create a ready-to-use image, since image must be embedded in a frame.

The optionnal text will appear above the image.

Arguments:

image -- DrawImage or str, DrawImage element or URL of the image

text -- str, text for the image

See Frame() initialization for the other arguments

Return: Frame

@classmethod
def text_frame( cls, text_or_element: collections.abc.Iterable[Element] | Element | str, text_style: str | None = None, name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any) -> Element:
318    @classmethod
319    def text_frame(
320        cls,
321        text_or_element: Iterable[Element] | Element | str,
322        text_style: str | None = None,
323        name: str | None = None,
324        draw_id: str | None = None,
325        style: str | None = None,
326        position: tuple | None = None,
327        size: tuple = ("1cm", "1cm"),
328        z_index: int = 0,
329        presentation_class: str | None = None,
330        anchor_type: str | None = None,
331        anchor_page: int | None = None,
332        layer: str | None = None,
333        presentation_style: str | None = None,
334        **kwargs: Any,
335    ) -> Element:
336        """Create a ready-to-use text box, since text box must be embedded in
337        a frame.
338
339        The optionnal text will appear above the image.
340
341        Arguments:
342
343            text_or_element -- str or Element, or list of them, text content
344                               of the text box.
345
346            text_style -- str, name of the style for the text
347
348            See Frame() initialization for the other arguments
349
350        Return: Frame
351        """
352        frame = cls(
353            name=name,
354            draw_id=draw_id,
355            style=style,
356            position=position,
357            size=size,
358            z_index=z_index,
359            presentation_class=presentation_class,
360            anchor_type=anchor_type,
361            anchor_page=anchor_page,
362            layer=layer,
363            presentation_style=presentation_style,
364            **kwargs,
365        )
366        frame.set_text_box(text_or_element, text_style)
367        return frame

Create a ready-to-use text box, since text box must be embedded in a frame.

The optionnal text will appear above the image.

Arguments:

text_or_element -- str or Element, or list of them, text content
                   of the text box.

text_style -- str, name of the style for the text

See Frame() initialization for the other arguments

Return: Frame

text_content: str
369    @property
370    def text_content(self) -> str:
371        text_box = self.get_element("draw:text-box")
372        if text_box is None:
373            return ""
374        return text_box.text_content

Get / set the text of the embedded paragraph, including embeded annotations, cells...

Set create a paragraph if missing

def get_image( self, position: int = 0, name: str | None = None, url: str | None = None, content: str | None = None) -> Element | None:
388    def get_image(
389        self,
390        position: int = 0,
391        name: str | None = None,
392        url: str | None = None,
393        content: str | None = None,
394    ) -> Element | None:
395        return self.get_element("draw:image")

Return the image matching the criteria.

Arguments:

position -- int

name -- str

url -- str regex

content -- str regex

Return: Element or None if not found

def set_image( self, url_or_element: Element | str) -> Element:
397    def set_image(self, url_or_element: Element | str) -> Element:
398        image = self.get_image()
399        if image is None:
400            if isinstance(url_or_element, Element):
401                image = url_or_element
402                self.append(image)
403            else:
404                image = DrawImage(url_or_element)
405                self.append(image)
406        else:
407            if isinstance(url_or_element, Element):
408                image.delete()
409                image = url_or_element
410                self.append(image)
411            else:
412                image.set_url(url_or_element)  # type: ignore
413        return image
def get_text_box(self) -> Element | None:
415    def get_text_box(self) -> Element | None:
416        return self.get_element("draw:text-box")
def set_text_box( self, text_or_element: collections.abc.Iterable[Element | str] | Element | str, text_style: str | None = None) -> Element:
418    def set_text_box(
419        self,
420        text_or_element: Iterable[Element | str] | Element | str,
421        text_style: str | None = None,
422    ) -> Element:
423        text_box = self.get_text_box()
424        if text_box is None:
425            text_box = Element.from_tag("draw:text-box")
426            self.append(text_box)
427        else:
428            text_box.clear()
429        if isinstance(text_or_element, (Element, str)):
430            text_or_element_list: Iterable[Element | str] = [text_or_element]
431        else:
432            text_or_element_list = text_or_element
433        for item in text_or_element_list:
434            if isinstance(item, str):
435                text_box.append(Paragraph(item, style=text_style))
436            else:
437                text_box.append(item)
438        return text_box
def get_formatted_text(self, context: dict | None = None) -> str:
449    def get_formatted_text(  # noqa:  C901
450        self,
451        context: dict | None = None,
452    ) -> str:
453        if not context:
454            context = {}
455        result = []
456        for element in self.children:
457            tag = element.tag
458            if tag == "draw:image":
459                if context["rst_mode"]:
460                    filename = element.get_attribute("xlink:href")
461
462                    # Compute width and height
463                    width, height = self.size
464                    if width is not None:
465                        width = Unit(width)
466                        width = width.convert("px", DPI)
467                    if height is not None:
468                        height = Unit(height)
469                        height = height.convert("px", DPI)
470
471                    # Insert or not ?
472                    if context["no_img_level"]:
473                        context["img_counter"] += 1
474                        ref = f"|img{context['img_counter']}|"
475                        result.append(ref)
476                        context["images"].append((ref, filename, (width, height)))
477                    else:
478                        result.append(f"\n.. image:: {filename}\n")
479                        if width is not None:
480                            result.append(f"   :width: {width}\n")
481                        if height is not None:
482                            result.append(f"   :height: {height}\n")
483                else:
484                    result.append(f"[Image {element.get_attribute('xlink:href')}]\n")
485            elif tag == "draw:text-box":
486                result.append(self._get_formatted_text_subresult(context, element))
487            else:
488                result.append(element.get_formatted_text(context))
489        result.append("\n")
490        return "".join(result)

This function should return a beautiful version of the text.

name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
draw_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
width: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
height: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
pos_x: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
pos_y: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
presentation_class: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
layer: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
presentation_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.AnchorMix
ANCHOR_VALUE_CHOICE
anchor_type
anchor_page
odfdo.frame.PosMix
position
odfdo.frame.ZMix
z_index
odfdo.frame.SizeMix
size
class HeaderRows(odfdo.Element):
32class HeaderRows(Element):
33    _tag = "table:table-header-rows"
34    _caching = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class IndexTitle(odfdo.Element):
40class IndexTitle(Element):
41    """The "text:index-title" element contains the title of an index.
42
43    The element has the following attributes:
44    text:name, text:protected, text:protection-key,
45    text:protection-key-digest-algorithm, text:style-name, xml:id.
46
47    The actual title is stored in a child element
48    """
49
50    _tag = "text:index-title"
51    _properties = (
52        PropDef("name", "text:name"),
53        PropDef("style", "text:style-name"),
54        PropDef("xml_id", "xml:id"),
55        PropDef("protected", "text:protected"),
56        PropDef("protection_key", "text:protection-key"),
57        PropDef(
58            "protection_key_digest_algorithm", "text:protection-key-digest-algorithm"
59        ),
60    )
61
62    def __init__(
63        self,
64        name: str | None = None,
65        style: str | None = None,
66        title_text: str | None = None,
67        title_text_style: str | None = None,
68        xml_id: str | None = None,
69        **kwargs: Any,
70    ) -> None:
71        super().__init__(**kwargs)
72        if self._do_init:
73            if name:
74                self.name = name
75            if style:
76                self.style = style
77            if xml_id:
78                self.xml_id = xml_id
79            if title_text:
80                self.set_title_text(title_text, title_text_style)
81
82    def set_title_text(
83        self,
84        title_text: str,
85        title_text_style: str | None = None,
86    ) -> None:
87        title = Paragraph(title_text, style=title_text_style)
88        self.append(title)

The "text:index-title" element contains the title of an index.

The element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name, xml:id.

The actual title is stored in a child element

IndexTitle( name: str | None = None, style: str | None = None, title_text: str | None = None, title_text_style: str | None = None, xml_id: str | None = None, **kwargs: Any)
62    def __init__(
63        self,
64        name: str | None = None,
65        style: str | None = None,
66        title_text: str | None = None,
67        title_text_style: str | None = None,
68        xml_id: str | None = None,
69        **kwargs: Any,
70    ) -> None:
71        super().__init__(**kwargs)
72        if self._do_init:
73            if name:
74                self.name = name
75            if style:
76                self.style = style
77            if xml_id:
78                self.xml_id = xml_id
79            if title_text:
80                self.set_title_text(title_text, title_text_style)
def set_title_text(self, title_text: str, title_text_style: str | None = None) -> None:
82    def set_title_text(
83        self,
84        title_text: str,
85        title_text_style: str | None = None,
86    ) -> None:
87        title = Paragraph(title_text, style=title_text_style)
88        self.append(title)
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
xml_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
protected: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
protection_key: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
protection_key_digest_algorithm: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class IndexTitleTemplate(odfdo.Element):
447class IndexTitleTemplate(Element):
448    """ODF "text:index-title-template"
449
450    Arguments:
451
452        style -- str
453    """
454
455    _tag = "text:index-title-template"
456    _properties = (PropDef("style", "text:style-name"),)
457
458    def __init__(self, style: str | None = None, **kwargs: Any) -> None:
459        super().__init__(**kwargs)
460        if self._do_init and style:
461            self.style = style

ODF "text:index-title-template"

Arguments:

style -- str
IndexTitleTemplate(style: str | None = None, **kwargs: Any)
458    def __init__(self, style: str | None = None, **kwargs: Any) -> None:
459        super().__init__(**kwargs)
460        if self._do_init and style:
461            self.style = style
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
LAST_CHILD = 1
class LineBreak(odfdo.Element):
223class LineBreak(Element):
224    """This element represents a line break "text:line-break" """
225
226    _tag = "text:line-break"
227
228    def __init__(self, **kwargs: Any) -> None:
229        super().__init__(**kwargs)

This element represents a line break "text:line-break"

LineBreak(**kwargs: Any)
228    def __init__(self, **kwargs: Any) -> None:
229        super().__init__(**kwargs)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class LineShape(odfdo.shapes.ShapeBase):
 89class LineShape(ShapeBase):
 90    """Create a line shape.
 91
 92    Arguments:
 93
 94        style -- str
 95
 96        text_style -- str
 97
 98        draw_id -- str
 99
100        layer -- str
101
102        p1 -- (str, str)
103
104        p2 -- (str, str)
105    """
106
107    _tag = "draw:line"
108    _properties: tuple[PropDef, ...] = (
109        PropDef("x1", "svg:x1"),
110        PropDef("y1", "svg:y1"),
111        PropDef("x2", "svg:x2"),
112        PropDef("y2", "svg:y2"),
113    )
114
115    def __init__(
116        self,
117        style: str | None = None,
118        text_style: str | None = None,
119        draw_id: str | None = None,
120        layer: str | None = None,
121        p1: tuple | None = None,
122        p2: tuple | None = None,
123        **kwargs: Any,
124    ) -> None:
125        kwargs.update(
126            {
127                "style": style,
128                "text_style": text_style,
129                "draw_id": draw_id,
130                "layer": layer,
131            }
132        )
133        super().__init__(**kwargs)
134        if self._do_init:
135            if p1:
136                self.x1 = p1[0]
137                self.y1 = p1[1]
138            if p2:
139                self.x2 = p2[0]
140                self.y2 = p2[1]

Create a line shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

p1 -- (str, str)

p2 -- (str, str)
LineShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, p1: tuple | None = None, p2: tuple | None = None, **kwargs: Any)
115    def __init__(
116        self,
117        style: str | None = None,
118        text_style: str | None = None,
119        draw_id: str | None = None,
120        layer: str | None = None,
121        p1: tuple | None = None,
122        p2: tuple | None = None,
123        **kwargs: Any,
124    ) -> None:
125        kwargs.update(
126            {
127                "style": style,
128                "text_style": text_style,
129                "draw_id": draw_id,
130                "layer": layer,
131            }
132        )
133        super().__init__(**kwargs)
134        if self._do_init:
135            if p1:
136                self.x1 = p1[0]
137                self.y1 = p1[1]
138            if p2:
139                self.x2 = p2[0]
140                self.y2 = p2[1]
x1: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
y1: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
x2: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
y2: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class List(odfdo.Element):
 68class List(Element):
 69    """ODF List "text:list"."""
 70
 71    _tag = "text:list"
 72    _properties = (PropDef("style", "text:style-name"),)
 73
 74    def __init__(
 75        self,
 76        list_content: str | Element | Iterable[str | Element] | None = None,
 77        style: str | None = None,
 78        **kwargs: Any,
 79    ) -> None:
 80        """Create a list element, optionaly loading the list by a list of
 81        item (str or elements).
 82
 83        The list_content argument is just a shortcut for the most common case.
 84        To create more complex lists, first create an empty list, and fill it
 85        afterwards.
 86
 87        Arguments:
 88
 89            list_content -- str or Element, or a list of str or Element
 90
 91            style -- str
 92        """
 93        super().__init__(**kwargs)
 94        if self._do_init:
 95            if list_content:
 96                if isinstance(list_content, (Element, str)):
 97                    self.append(ListItem(list_content))
 98                elif hasattr(list_content, "__iter__"):
 99                    for item in list_content:
100                        self.append(ListItem(item))
101            if style is not None:
102                self.style = style
103
104    def get_items(self, content: str | None = None) -> list[Element]:
105        """Return all the list items that match the criteria.
106
107        Arguments:
108
109            style -- str
110
111            content -- str regex
112
113        Return: list of Element
114        """
115        return self._filtered_elements("text:list-item", content=content)
116
117    def get_item(
118        self,
119        position: int = 0,
120        content: str | None = None,
121    ) -> Element | None:
122        """Return the list item that matches the criteria. In nested lists,
123        return the list item that really contains that content.
124
125        Arguments:
126
127            position -- int
128
129            content -- str regex
130
131        Return: Element or None if not found
132        """
133        # Custom implementation because of nested lists
134        if content:
135            # Don't search recursively but on the very own paragraph(s) of
136            # each list item
137            for paragraph in self.get_elements("descendant::text:p"):
138                if paragraph.match(content):
139                    return paragraph.get_element("parent::text:list-item")
140            return None
141        return self._filtered_element("text:list-item", position)
142
143    def set_list_header(
144        self,
145        text_or_element: str | Element | Iterable[str | Element],
146    ) -> None:
147        if isinstance(text_or_element, (str, Element)):
148            actual_list: list[str | Element] | tuple = [text_or_element]
149        elif isinstance(text_or_element, (list, tuple)):
150            actual_list = text_or_element
151        else:
152            raise TypeError
153        # Remove existing header
154        for element in self.get_elements("text:p"):
155            self.delete(element)
156        for paragraph in reversed(actual_list):
157            if isinstance(paragraph, str):
158                paragraph = Paragraph(paragraph)
159            self.insert(paragraph, FIRST_CHILD)
160
161    def insert_item(
162        self,
163        item: ListItem | str | Element | None,
164        position: int | None = None,
165        before: Element | None = None,
166        after: Element | None = None,
167    ) -> None:
168        if not isinstance(item, ListItem):
169            item = ListItem(item)
170        if before is not None:
171            before.insert(item, xmlposition=PREV_SIBLING)
172        elif after is not None:
173            after.insert(item, xmlposition=NEXT_SIBLING)
174        elif position is not None:
175            self.insert(item, position=position)
176        else:
177            raise ValueError("Position must be defined")
178
179    def append_item(
180        self,
181        item: ListItem | str | Element | None,
182    ) -> None:
183        if not isinstance(item, ListItem):
184            item = ListItem(item)
185        self.append(item)
186
187    def get_formatted_text(self, context: dict | None = None) -> str:
188        if context is None:
189            context = {}
190        rst_mode = context["rst_mode"]
191        result = []
192        if rst_mode:
193            result.append("\n")
194        for list_item in self.get_elements("text:list-item"):
195            textbuf = []
196            for child in list_item.children:
197                text = child.get_formatted_text(context)
198                tag = child.tag
199                if tag == "text:h":
200                    # A title in a list is a bug
201                    return text
202                if tag == "text:list" and not text.lstrip().startswith("-"):
203                    # If the list didn't indent, don't either
204                    # (inner title)
205                    return text
206                textbuf.append(text)
207            text_sum = "".join(textbuf)
208            text_sum = text_sum.strip("\n")
209            # Indent the text
210            text_sum = text_sum.replace("\n", "\n  ")
211            text_sum = f"- {text_sum}\n"
212            result.append(text_sum)
213        if rst_mode:
214            result.append("\n")
215        return "".join(result)

ODF List "text:list".

List( list_content: str | Element | collections.abc.Iterable[str | Element] | None = None, style: str | None = None, **kwargs: Any)
 74    def __init__(
 75        self,
 76        list_content: str | Element | Iterable[str | Element] | None = None,
 77        style: str | None = None,
 78        **kwargs: Any,
 79    ) -> None:
 80        """Create a list element, optionaly loading the list by a list of
 81        item (str or elements).
 82
 83        The list_content argument is just a shortcut for the most common case.
 84        To create more complex lists, first create an empty list, and fill it
 85        afterwards.
 86
 87        Arguments:
 88
 89            list_content -- str or Element, or a list of str or Element
 90
 91            style -- str
 92        """
 93        super().__init__(**kwargs)
 94        if self._do_init:
 95            if list_content:
 96                if isinstance(list_content, (Element, str)):
 97                    self.append(ListItem(list_content))
 98                elif hasattr(list_content, "__iter__"):
 99                    for item in list_content:
100                        self.append(ListItem(item))
101            if style is not None:
102                self.style = style

Create a list element, optionaly loading the list by a list of item (str or elements).

The list_content argument is just a shortcut for the most common case. To create more complex lists, first create an empty list, and fill it afterwards.

Arguments:

list_content -- str or Element, or a list of str or Element

style -- str
def get_items(self, content: str | None = None) -> list[Element]:
104    def get_items(self, content: str | None = None) -> list[Element]:
105        """Return all the list items that match the criteria.
106
107        Arguments:
108
109            style -- str
110
111            content -- str regex
112
113        Return: list of Element
114        """
115        return self._filtered_elements("text:list-item", content=content)

Return all the list items that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Element

def get_item( self, position: int = 0, content: str | None = None) -> Element | None:
117    def get_item(
118        self,
119        position: int = 0,
120        content: str | None = None,
121    ) -> Element | None:
122        """Return the list item that matches the criteria. In nested lists,
123        return the list item that really contains that content.
124
125        Arguments:
126
127            position -- int
128
129            content -- str regex
130
131        Return: Element or None if not found
132        """
133        # Custom implementation because of nested lists
134        if content:
135            # Don't search recursively but on the very own paragraph(s) of
136            # each list item
137            for paragraph in self.get_elements("descendant::text:p"):
138                if paragraph.match(content):
139                    return paragraph.get_element("parent::text:list-item")
140            return None
141        return self._filtered_element("text:list-item", position)

Return the list item that matches the criteria. In nested lists, return the list item that really contains that content.

Arguments:

position -- int

content -- str regex

Return: Element or None if not found

def set_list_header( self, text_or_element: str | Element | collections.abc.Iterable[str | Element]) -> None:
143    def set_list_header(
144        self,
145        text_or_element: str | Element | Iterable[str | Element],
146    ) -> None:
147        if isinstance(text_or_element, (str, Element)):
148            actual_list: list[str | Element] | tuple = [text_or_element]
149        elif isinstance(text_or_element, (list, tuple)):
150            actual_list = text_or_element
151        else:
152            raise TypeError
153        # Remove existing header
154        for element in self.get_elements("text:p"):
155            self.delete(element)
156        for paragraph in reversed(actual_list):
157            if isinstance(paragraph, str):
158                paragraph = Paragraph(paragraph)
159            self.insert(paragraph, FIRST_CHILD)
def insert_item( self, item: ListItem | str | Element | None, position: int | None = None, before: Element | None = None, after: Element | None = None) -> None:
161    def insert_item(
162        self,
163        item: ListItem | str | Element | None,
164        position: int | None = None,
165        before: Element | None = None,
166        after: Element | None = None,
167    ) -> None:
168        if not isinstance(item, ListItem):
169            item = ListItem(item)
170        if before is not None:
171            before.insert(item, xmlposition=PREV_SIBLING)
172        elif after is not None:
173            after.insert(item, xmlposition=NEXT_SIBLING)
174        elif position is not None:
175            self.insert(item, position=position)
176        else:
177            raise ValueError("Position must be defined")
def append_item( self, item: ListItem | str | Element | None) -> None:
179    def append_item(
180        self,
181        item: ListItem | str | Element | None,
182    ) -> None:
183        if not isinstance(item, ListItem):
184            item = ListItem(item)
185        self.append(item)
def get_formatted_text(self, context: dict | None = None) -> str:
187    def get_formatted_text(self, context: dict | None = None) -> str:
188        if context is None:
189            context = {}
190        rst_mode = context["rst_mode"]
191        result = []
192        if rst_mode:
193            result.append("\n")
194        for list_item in self.get_elements("text:list-item"):
195            textbuf = []
196            for child in list_item.children:
197                text = child.get_formatted_text(context)
198                tag = child.tag
199                if tag == "text:h":
200                    # A title in a list is a bug
201                    return text
202                if tag == "text:list" and not text.lstrip().startswith("-"):
203                    # If the list didn't indent, don't either
204                    # (inner title)
205                    return text
206                textbuf.append(text)
207            text_sum = "".join(textbuf)
208            text_sum = text_sum.strip("\n")
209            # Indent the text
210            text_sum = text_sum.replace("\n", "\n  ")
211            text_sum = f"- {text_sum}\n"
212            result.append(text_sum)
213        if rst_mode:
214            result.append("\n")
215        return "".join(result)

This function should return a beautiful version of the text.

style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ListItem(odfdo.Element):
41class ListItem(Element):
42    """ODF element "text:list-item", item of a List."""
43
44    _tag = "text:list-item"
45
46    def __init__(
47        self,
48        text_or_element: str | Element | None = None,
49        **kwargs: Any,
50    ) -> None:
51        """Create a list item element, optionaly passing at creation time a
52        string or Element as content.
53
54        Arguments:
55
56            text_or_element -- str or ODF Element
57        """
58        super().__init__(**kwargs)
59        if self._do_init:
60            if isinstance(text_or_element, str):
61                self.text_content = text_or_element
62            elif isinstance(text_or_element, Element):
63                self.append(text_or_element)
64            elif text_or_element is not None:
65                raise TypeError("Expected str or Element")

ODF element "text:list-item", item of a List.

ListItem( text_or_element: str | Element | None = None, **kwargs: Any)
46    def __init__(
47        self,
48        text_or_element: str | Element | None = None,
49        **kwargs: Any,
50    ) -> None:
51        """Create a list item element, optionaly passing at creation time a
52        string or Element as content.
53
54        Arguments:
55
56            text_or_element -- str or ODF Element
57        """
58        super().__init__(**kwargs)
59        if self._do_init:
60            if isinstance(text_or_element, str):
61                self.text_content = text_or_element
62            elif isinstance(text_or_element, Element):
63                self.append(text_or_element)
64            elif text_or_element is not None:
65                raise TypeError("Expected str or Element")

Create a list item element, optionaly passing at creation time a string or Element as content.

Arguments:

text_or_element -- str or ODF Element
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Manifest(odfdo.XmlPart):
 31class Manifest(XmlPart):
 32    def get_paths(self) -> list[Element | Text]:
 33        """Return the list of full paths in the manifest.
 34
 35        Return: list of str
 36        """
 37        xpath_query = "//manifest:file-entry/attribute::manifest:full-path"
 38        return self.xpath(xpath_query)
 39
 40    def _file_entry(self, full_path: str) -> Element:
 41        xpath_query = (
 42            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
 43        )
 44        result = self.xpath(xpath_query)
 45        if not result:
 46            raise KeyError(f"Path not found: '{full_path}'")
 47        return result[0]  # type: ignore
 48
 49    def get_path_medias(self) -> list[tuple]:
 50        """Return the list of (full_path, media_type) pairs in the manifest.
 51
 52        Return: list of str tuples
 53        """
 54        xpath_query = "//manifest:file-entry"
 55        result = []
 56        for file_entry in self.xpath(xpath_query):
 57            if not isinstance(file_entry, Element):
 58                continue
 59            result.append(
 60                (
 61                    file_entry.get_attribute_string("manifest:full-path"),
 62                    file_entry.get_attribute_string("manifest:media-type"),
 63                )
 64            )
 65        return result
 66
 67    def get_media_type(self, full_path: str) -> str | None:
 68        """Get the media type of an existing path.
 69
 70        Return: str
 71        """
 72        xpath_query = (
 73            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
 74            "/attribute::manifest:media-type"
 75        )
 76        result = self.xpath(xpath_query)
 77        if not result:
 78            return None
 79        return str(result[0])
 80
 81    def set_media_type(self, full_path: str, media_type: str) -> None:
 82        """Set the media type of an existing path.
 83
 84        Arguments:
 85
 86            full_path -- str
 87
 88            media_type -- str
 89        """
 90        file_entry = self._file_entry(full_path)
 91        file_entry.set_attribute("manifest:media-type", media_type)
 92
 93    @staticmethod
 94    def make_file_entry(full_path: str, media_type: str) -> Element:
 95        tag = (
 96            f"<manifest:file-entry "
 97            f'manifest:media-type="{media_type}" '
 98            f'manifest:full-path="{full_path}"/>'
 99        )
100        return Element.from_tag(tag)
101
102    def add_full_path(self, full_path: str, media_type: str = "") -> None:
103        # Existing?
104        existing = self.get_media_type(full_path)
105        if existing is not None:
106            self.set_media_type(full_path, media_type)
107        root = self.root
108        root.append(self.make_file_entry(full_path, media_type))
109
110    def del_full_path(self, full_path: str) -> None:
111        file_entry = self._file_entry(full_path)
112        self.root.delete(file_entry)

Representation of an XML part.

Abstraction of the XML library behind.

def get_paths(self) -> list[Element | Text]:
32    def get_paths(self) -> list[Element | Text]:
33        """Return the list of full paths in the manifest.
34
35        Return: list of str
36        """
37        xpath_query = "//manifest:file-entry/attribute::manifest:full-path"
38        return self.xpath(xpath_query)

Return the list of full paths in the manifest.

Return: list of str

def get_path_medias(self) -> list[tuple]:
49    def get_path_medias(self) -> list[tuple]:
50        """Return the list of (full_path, media_type) pairs in the manifest.
51
52        Return: list of str tuples
53        """
54        xpath_query = "//manifest:file-entry"
55        result = []
56        for file_entry in self.xpath(xpath_query):
57            if not isinstance(file_entry, Element):
58                continue
59            result.append(
60                (
61                    file_entry.get_attribute_string("manifest:full-path"),
62                    file_entry.get_attribute_string("manifest:media-type"),
63                )
64            )
65        return result

Return the list of (full_path, media_type) pairs in the manifest.

Return: list of str tuples

def get_media_type(self, full_path: str) -> str | None:
67    def get_media_type(self, full_path: str) -> str | None:
68        """Get the media type of an existing path.
69
70        Return: str
71        """
72        xpath_query = (
73            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
74            "/attribute::manifest:media-type"
75        )
76        result = self.xpath(xpath_query)
77        if not result:
78            return None
79        return str(result[0])

Get the media type of an existing path.

Return: str

def set_media_type(self, full_path: str, media_type: str) -> None:
81    def set_media_type(self, full_path: str, media_type: str) -> None:
82        """Set the media type of an existing path.
83
84        Arguments:
85
86            full_path -- str
87
88            media_type -- str
89        """
90        file_entry = self._file_entry(full_path)
91        file_entry.set_attribute("manifest:media-type", media_type)

Set the media type of an existing path.

Arguments:

full_path -- str

media_type -- str
@staticmethod
def make_file_entry(full_path: str, media_type: str) -> Element:
 93    @staticmethod
 94    def make_file_entry(full_path: str, media_type: str) -> Element:
 95        tag = (
 96            f"<manifest:file-entry "
 97            f'manifest:media-type="{media_type}" '
 98            f'manifest:full-path="{full_path}"/>'
 99        )
100        return Element.from_tag(tag)
def add_full_path(self, full_path: str, media_type: str = '') -> None:
102    def add_full_path(self, full_path: str, media_type: str = "") -> None:
103        # Existing?
104        existing = self.get_media_type(full_path)
105        if existing is not None:
106            self.set_media_type(full_path, media_type)
107        root = self.root
108        root.append(self.make_file_entry(full_path, media_type))
def del_full_path(self, full_path: str) -> None:
110    def del_full_path(self, full_path: str) -> None:
111        file_entry = self._file_entry(full_path)
112        self.root.delete(file_entry)
class Meta(odfdo.XmlPart):
 43class Meta(XmlPart):
 44    def __init__(self, *args: Any, **kwargs: Any) -> None:
 45        super().__init__(*args, **kwargs)
 46        self._generator_modified: bool = False
 47
 48    def get_meta_body(self) -> Element:
 49        return self.get_element("//office:meta")
 50
 51    def get_title(self) -> str | None:
 52        """Get the title of the document.
 53
 54        This is not the first heading but the title metadata.
 55
 56        Return: str (or None if inexistant)
 57        """
 58        element = self.get_element("//dc:title")
 59        if element is None:
 60            return None
 61        return element.text
 62
 63    def set_title(self, title: str) -> None:
 64        """Set the title of the document.
 65
 66        This is not the first heading but the title metadata.
 67
 68        Arguments:
 69
 70            title -- str
 71        """
 72        element = self.get_element("//dc:title")
 73        if element is None:
 74            element = Element.from_tag("dc:title")
 75            self.get_meta_body().append(element)
 76        element.text = title
 77
 78    def get_description(self) -> str | None:
 79        """Get the description of the document. Also known as comments.
 80
 81        Return: str (or None if inexistant)
 82        """
 83        element = self.get_element("//dc:description")
 84        if element is None:
 85            return None
 86        return element.text
 87
 88    # As named in OOo
 89    get_comments = get_description
 90
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description
103
104    set_comments = set_description
105
106    def get_subject(self) -> str | None:
107        """Get the subject of the document.
108
109        Return: str (or None if inexistant)
110        """
111        element = self.get_element("//dc:subject")
112        if element is None:
113            return None
114        return element.text
115
116    def set_subject(self, subject: str) -> None:
117        """Set the subject of the document.
118
119        Arguments:
120
121            subject -- str
122        """
123        element = self.get_element("//dc:subject")
124        if element is None:
125            element = Element.from_tag("dc:subject")
126            self.get_meta_body().append(element)
127        element.text = subject
128
129    def get_language(self) -> str | None:
130        """Get the language code of the document.
131
132        Return: str (or None if inexistant)
133
134        Example::
135
136            >>> document.meta.get_language()
137            fr-FR
138        """
139        element = self.get_element("//dc:language")
140        if element is None:
141            return None
142        return element.text
143
144    def set_language(self, language: str) -> None:
145        """Set the language code of the document.
146
147        Arguments:
148
149            language -- str
150
151        Example::
152
153            >>> document.meta.set_language('fr-FR')
154        """
155        language = str(language)
156        if not self._is_RFC3066(language):
157            raise TypeError(
158                'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)'
159            )
160        element = self.get_element("//dc:language")
161        if element is None:
162            element = Element.from_tag("dc:language")
163            self.get_meta_body().append(element)
164        element.text = language
165
166    @staticmethod
167    def _is_RFC3066(lang: str) -> bool:
168        def test_part1(part1: str) -> bool:
169            if not 2 <= len(part1) <= 3:
170                return False
171            return all(x in ascii_letters for x in part1)
172
173        def test_part2(part2: str) -> bool:
174            return all(x in ascii_letters or x in digits for x in part2)
175
176        if not lang or not isinstance(lang, str):
177            return False
178        if "-" not in lang:
179            return test_part1(lang)
180        parts = lang.split("-")
181        if len(parts) > 3:
182            return False
183        if not test_part1(parts[0]):
184            return False
185        return all(test_part2(p) for p in parts[1:])
186
187    def get_modification_date(self) -> datetime | None:
188        """Get the last modified date of the document.
189
190        Return: datetime (or None if inexistant)
191        """
192        element = self.get_element("//dc:date")
193        if element is None:
194            return None
195        modification_date = element.text
196        return DateTime.decode(modification_date)
197
198    def set_modification_date(self, date: datetime) -> None:
199        """Set the last modified date of the document.
200
201        Arguments:
202
203            date -- datetime
204        """
205        element = self.get_element("//dc:date")
206        if element is None:
207            element = Element.from_tag("dc:date")
208            self.get_meta_body().append(element)
209        element.text = DateTime.encode(date)
210
211    def get_creation_date(self) -> datetime | None:
212        """Get the creation date of the document.
213
214        Return: datetime (or None if inexistant)
215        """
216        element = self.get_element("//meta:creation-date")
217        if element is None:
218            return None
219        creation_date = element.text
220        return DateTime.decode(creation_date)
221
222    def set_creation_date(self, date: datetime) -> None:
223        """Set the creation date of the document.
224
225        Arguments:
226
227            date -- datetime
228        """
229        element = self.get_element("//meta:creation-date")
230        if element is None:
231            element = Element.from_tag("meta:creation-date")
232            self.get_meta_body().append(element)
233        element.text = DateTime.encode(date)
234
235    def get_initial_creator(self) -> str | None:
236        """Get the first creator of the document.
237
238        Return: str (or None if inexistant)
239
240        Example::
241
242            >>> document.meta.get_initial_creator()
243            Unknown
244        """
245        element = self.get_element("//meta:initial-creator")
246        if element is None:
247            return None
248        return element.text
249
250    def set_initial_creator(self, creator: str) -> None:
251        """Set the first creator of the document.
252
253        Arguments:
254
255            creator -- str
256
257        Example::
258
259            >>> document.meta.set_initial_creator("Plato")
260        """
261        element = self.get_element("//meta:initial-creator")
262        if element is None:
263            element = Element.from_tag("meta:initial-creator")
264            self.get_meta_body().append(element)
265        element.text = creator
266
267    def get_creator(self) -> str | None:
268        """Get the creator of the document.
269
270        Return: str (or None if inexistant)
271
272        Example::
273
274            >>> document.meta.get_creator()
275            Unknown
276        """
277        element = self.get_element("//dc:creator")
278        if element is None:
279            return None
280        return element.text
281
282    def set_creator(self, creator: str) -> None:
283        """Set the creator of the document.
284
285        Arguments:
286
287            creator -- str
288
289        Example::
290
291            >>> document.meta.set_creator("Plato")
292        """
293        element = self.get_element("//dc:creator")
294        if element is None:
295            element = Element.from_tag("dc:creator")
296            self.get_meta_body().append(element)
297        element.text = creator
298
299    def get_keywords(self) -> str | None:
300        """Get the keywords of the document. Return the field as-is, without
301        any assumption on the keyword separator.
302
303        Return: str (or None if inexistant)
304        """
305        element = self.get_element("//meta:keyword")
306        if element is None:
307            return None
308        return element.text
309
310    def set_keywords(self, keywords: str) -> None:
311        """Set the keywords of the document. Although the name is plural, a
312        str string is required, so join your list first.
313
314        Arguments:
315
316            keywords -- str
317        """
318        element = self.get_element("//meta:keyword")
319        if element is None:
320            element = Element.from_tag("meta:keyword")
321            self.get_meta_body().append(element)
322        element.text = keywords
323
324    def get_editing_duration(self) -> timedelta | None:
325        """Get the time the document was edited, as reported by the
326        generator.
327
328        Return: timedelta (or None if inexistant)
329        """
330        element = self.get_element("//meta:editing-duration")
331        if element is None:
332            return None
333        duration = element.text
334        return Duration.decode(duration)
335
336    def set_editing_duration(self, duration: timedelta) -> None:
337        """Set the time the document was edited.
338
339        Arguments:
340
341            duration -- timedelta
342        """
343        if not isinstance(duration, timedelta):
344            raise TypeError("duration must be a timedelta")
345        element = self.get_element("//meta:editing-duration")
346        if element is None:
347            element = Element.from_tag("meta:editing-duration")
348            self.get_meta_body().append(element)
349        element.text = Duration.encode(duration)
350
351    def get_editing_cycles(self) -> int | None:
352        """Get the number of times the document was edited, as reported by
353        the generator.
354
355        Return: int (or None if inexistant)
356        """
357        element = self.get_element("//meta:editing-cycles")
358        if element is None:
359            return None
360        cycles = element.text
361        return int(cycles)
362
363    def set_editing_cycles(self, cycles: int) -> None:
364        """Set the number of times the document was edited.
365
366        Arguments:
367
368            cycles -- int
369        """
370        if not isinstance(cycles, int):
371            raise TypeError("cycles must be an int")
372        if cycles < 1:
373            raise ValueError("cycles must be a positive int")
374        element = self.get_element("//meta:editing-cycles")
375        if element is None:
376            element = Element.from_tag("meta:editing-cycles")
377            self.get_meta_body().append(element)
378        element.text = str(cycles)
379
380    def get_generator(self) -> str | None:
381        """Get the signature of the software that generated this document.
382
383        Return: str (or None if inexistant)
384
385        Example::
386
387            >>> document.meta.get_generator()
388            KOffice/2.0.0
389        """
390        element = self.get_element("//meta:generator")
391        if element is None:
392            return None
393        return element.text
394
395    def set_generator(self, generator: str) -> None:
396        """Set the signature of the software that generated this document.
397
398        Arguments:
399
400            generator -- str
401
402        Example::
403
404            >>> document.meta.set_generator("Odfdo experiment")
405        """
406        element = self.get_element("//meta:generator")
407        if element is None:
408            element = Element.from_tag("meta:generator")
409            self.get_meta_body().append(element)
410        element.text = generator
411        self._generator_modified = True
412
413    def set_generator_default(self) -> None:
414        """Set the signature of the software that generated this document
415        to ourself.
416
417        Example::
418
419            >>> document.meta.set_generator_default()
420        """
421        if not self._generator_modified:
422            self.set_generator(GENERATOR)
423
424    def get_statistic(self) -> dict[str, int] | None:
425        """Get the statistic from the software that generated this document.
426
427        Return: dict (or None if inexistant)
428
429        Example::
430
431            >>> document.get_statistic():
432            {'meta:table-count': 1,
433             'meta:image-count': 2,
434             'meta:object-count': 3,
435             'meta:page-count': 4,
436             'meta:paragraph-count': 5,
437             'meta:word-count': 6,
438             'meta:character-count': 7}
439        """
440        element = self.get_element("//meta:document-statistic")
441        if element is None:
442            return None
443        statistic = {}
444        for key, value in element.attributes.items():
445            statistic[to_str(key)] = int(value)
446        return statistic
447
448    def set_statistic(self, statistic: dict[str, int]) -> None:
449        """Set the statistic for the documents: number of words, paragraphs,
450        etc.
451
452        Arguments:
453
454            statistic -- dict
455
456        Example::
457
458            >>> statistic = {'meta:table-count': 1,
459                             'meta:image-count': 2,
460                             'meta:object-count': 3,
461                             'meta:page-count': 4,
462                             'meta:paragraph-count': 5,
463                             'meta:word-count': 6,
464                             'meta:character-count': 7}
465            >>> document.meta.set_statistic(statistic)
466        """
467        if not isinstance(statistic, dict):
468            raise TypeError("Statistic must be a dict")
469        element = self.get_element("//meta:document-statistic")
470        for key, value in statistic.items():
471            try:
472                ivalue = int(value)
473            except ValueError as e:
474                raise TypeError("Statistic value must be a int") from e
475            element.set_attribute(to_str(key), str(ivalue))
476
477    def get_user_defined_metadata(self) -> dict[str, Any]:
478        """Return a dict of str/value mapping.
479
480        Value types can be: Decimal, date, time, boolean or str.
481        """
482        result: dict[str, Any] = {}
483        for item in self.get_elements("//meta:user-defined"):
484            if not isinstance(item, Element):
485                continue
486            # Read the values
487            name = item.get_attribute_string("meta:name")
488            if name is None:
489                continue
490            value = self._get_meta_value(item)
491            result[name] = value
492        return result
493
494    def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None:
495        """Return the content of the user defined metadata of that name.
496        Return None if no name matchs or a dic of fields.
497
498        Arguments:
499
500            name -- string, name (meta:name content)
501        """
502        result = {}
503        found = False
504        for item in self.get_elements("//meta:user-defined"):
505            if not isinstance(item, Element):
506                continue
507            # Read the values
508            name = item.get_attribute("meta:name")
509            if name == keyname:
510                found = True
511                break
512        if not found:
513            return None
514        result["name"] = name
515        value, value_type, text = self._get_meta_value(item, full=True)  # type: ignore
516        result["value"] = value
517        result["value_type"] = value_type
518        result["text"] = text
519        return result
520
521    def set_user_defined_metadata(self, name: str, value: Any) -> None:
522        if isinstance(value, bool):
523            value_type = "boolean"
524            value = "true" if value else "false"
525        elif isinstance(value, (int, float, Decimal)):
526            value_type = "float"
527            value = str(value)
528        elif isinstance(value, dtdate):
529            value_type = "date"
530            value = str(Date.encode(value))
531        elif isinstance(value, datetime):
532            value_type = "date"
533            value = str(DateTime.encode(value))
534        elif isinstance(value, str):
535            value_type = "string"
536        elif isinstance(value, timedelta):
537            value_type = "time"
538            value = str(Duration.encode(value))
539        else:
540            raise TypeError('unexpected type "%s" for value' % type(value))
541        # Already the same element ?
542        for metadata in self.get_elements("//meta:user-defined"):
543            if not isinstance(metadata, Element):
544                continue
545            if metadata.get_attribute("meta:name") == name:
546                break
547        else:
548            metadata = Element.from_tag("meta:user-defined")
549            metadata.set_attribute("meta:name", name)
550            self.get_meta_body().append(metadata)
551        metadata.set_attribute("meta:value-type", value_type)
552        metadata.text = value
553
554    def _get_meta_value(
555        self, element: Element, full: bool = False
556    ) -> Any | tuple[Any, str, str]:
557        """get_value() deicated to the meta data part, for one meta element."""
558        if full:
559            return self._get_meta_value_full(element)
560        else:
561            return self._get_meta_value_full(element)[0]
562
563    @staticmethod
564    def _get_meta_value_full(element: Element) -> tuple[Any, str, str]:
565        """get_value deicated to the meta data part, for one meta element."""
566        # name = element.get_attribute('meta:name')
567        value_type = element.get_attribute_string("meta:value-type")
568        if value_type is None:
569            value_type = "string"
570        text = element.text
571        # Interpretation
572        if value_type == "boolean":
573            return (Boolean.decode(text), value_type, text)
574        if value_type in ("float", "percentage", "currency"):
575            return (Decimal(text), value_type, text)
576        if value_type == "date":
577            if "T" in text:
578                return (DateTime.decode(text), value_type, text)
579            else:
580                return (Date.decode(text), value_type, text)
581        if value_type == "string":
582            return (text, value_type, text)
583        if value_type == "time":
584            return (Duration.decode(text), value_type, text)
585        raise TypeError(f"Unknown value type: '{value_type!r}'")

Representation of an XML part.

Abstraction of the XML library behind.

Meta(*args: Any, **kwargs: Any)
44    def __init__(self, *args: Any, **kwargs: Any) -> None:
45        super().__init__(*args, **kwargs)
46        self._generator_modified: bool = False
def get_meta_body(self) -> Element:
48    def get_meta_body(self) -> Element:
49        return self.get_element("//office:meta")
def get_title(self) -> str | None:
51    def get_title(self) -> str | None:
52        """Get the title of the document.
53
54        This is not the first heading but the title metadata.
55
56        Return: str (or None if inexistant)
57        """
58        element = self.get_element("//dc:title")
59        if element is None:
60            return None
61        return element.text

Get the title of the document.

This is not the first heading but the title metadata.

Return: str (or None if inexistant)

def set_title(self, title: str) -> None:
63    def set_title(self, title: str) -> None:
64        """Set the title of the document.
65
66        This is not the first heading but the title metadata.
67
68        Arguments:
69
70            title -- str
71        """
72        element = self.get_element("//dc:title")
73        if element is None:
74            element = Element.from_tag("dc:title")
75            self.get_meta_body().append(element)
76        element.text = title

Set the title of the document.

This is not the first heading but the title metadata.

Arguments:

title -- str
def get_description(self) -> str | None:
78    def get_description(self) -> str | None:
79        """Get the description of the document. Also known as comments.
80
81        Return: str (or None if inexistant)
82        """
83        element = self.get_element("//dc:description")
84        if element is None:
85            return None
86        return element.text

Get the description of the document. Also known as comments.

Return: str (or None if inexistant)

def get_comments(self) -> str | None:
78    def get_description(self) -> str | None:
79        """Get the description of the document. Also known as comments.
80
81        Return: str (or None if inexistant)
82        """
83        element = self.get_element("//dc:description")
84        if element is None:
85            return None
86        return element.text

Get the description of the document. Also known as comments.

Return: str (or None if inexistant)

def set_description(self, description: str) -> None:
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description

Set the description of the document. Also known as comments.

Arguments:

description -- str
def set_comments(self, description: str) -> None:
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description

Set the description of the document. Also known as comments.

Arguments:

description -- str
def get_subject(self) -> str | None:
106    def get_subject(self) -> str | None:
107        """Get the subject of the document.
108
109        Return: str (or None if inexistant)
110        """
111        element = self.get_element("//dc:subject")
112        if element is None:
113            return None
114        return element.text

Get the subject of the document.

Return: str (or None if inexistant)

def set_subject(self, subject: str) -> None:
116    def set_subject(self, subject: str) -> None:
117        """Set the subject of the document.
118
119        Arguments:
120
121            subject -- str
122        """
123        element = self.get_element("//dc:subject")
124        if element is None:
125            element = Element.from_tag("dc:subject")
126            self.get_meta_body().append(element)
127        element.text = subject

Set the subject of the document.

Arguments:

subject -- str
def get_language(self) -> str | None:
129    def get_language(self) -> str | None:
130        """Get the language code of the document.
131
132        Return: str (or None if inexistant)
133
134        Example::
135
136            >>> document.meta.get_language()
137            fr-FR
138        """
139        element = self.get_element("//dc:language")
140        if element is None:
141            return None
142        return element.text

Get the language code of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_language()
fr-FR
def set_language(self, language: str) -> None:
144    def set_language(self, language: str) -> None:
145        """Set the language code of the document.
146
147        Arguments:
148
149            language -- str
150
151        Example::
152
153            >>> document.meta.set_language('fr-FR')
154        """
155        language = str(language)
156        if not self._is_RFC3066(language):
157            raise TypeError(
158                'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)'
159            )
160        element = self.get_element("//dc:language")
161        if element is None:
162            element = Element.from_tag("dc:language")
163            self.get_meta_body().append(element)
164        element.text = language

Set the language code of the document.

Arguments:

language -- str

Example::

>>> document.meta.set_language('fr-FR')
def get_modification_date(self) -> datetime.datetime | None:
187    def get_modification_date(self) -> datetime | None:
188        """Get the last modified date of the document.
189
190        Return: datetime (or None if inexistant)
191        """
192        element = self.get_element("//dc:date")
193        if element is None:
194            return None
195        modification_date = element.text
196        return DateTime.decode(modification_date)

Get the last modified date of the document.

Return: datetime (or None if inexistant)

def set_modification_date(self, date: datetime.datetime) -> None:
198    def set_modification_date(self, date: datetime) -> None:
199        """Set the last modified date of the document.
200
201        Arguments:
202
203            date -- datetime
204        """
205        element = self.get_element("//dc:date")
206        if element is None:
207            element = Element.from_tag("dc:date")
208            self.get_meta_body().append(element)
209        element.text = DateTime.encode(date)

Set the last modified date of the document.

Arguments:

date -- datetime
def get_creation_date(self) -> datetime.datetime | None:
211    def get_creation_date(self) -> datetime | None:
212        """Get the creation date of the document.
213
214        Return: datetime (or None if inexistant)
215        """
216        element = self.get_element("//meta:creation-date")
217        if element is None:
218            return None
219        creation_date = element.text
220        return DateTime.decode(creation_date)

Get the creation date of the document.

Return: datetime (or None if inexistant)

def set_creation_date(self, date: datetime.datetime) -> None:
222    def set_creation_date(self, date: datetime) -> None:
223        """Set the creation date of the document.
224
225        Arguments:
226
227            date -- datetime
228        """
229        element = self.get_element("//meta:creation-date")
230        if element is None:
231            element = Element.from_tag("meta:creation-date")
232            self.get_meta_body().append(element)
233        element.text = DateTime.encode(date)

Set the creation date of the document.

Arguments:

date -- datetime
def get_initial_creator(self) -> str | None:
235    def get_initial_creator(self) -> str | None:
236        """Get the first creator of the document.
237
238        Return: str (or None if inexistant)
239
240        Example::
241
242            >>> document.meta.get_initial_creator()
243            Unknown
244        """
245        element = self.get_element("//meta:initial-creator")
246        if element is None:
247            return None
248        return element.text

Get the first creator of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_initial_creator()
Unknown
def set_initial_creator(self, creator: str) -> None:
250    def set_initial_creator(self, creator: str) -> None:
251        """Set the first creator of the document.
252
253        Arguments:
254
255            creator -- str
256
257        Example::
258
259            >>> document.meta.set_initial_creator("Plato")
260        """
261        element = self.get_element("//meta:initial-creator")
262        if element is None:
263            element = Element.from_tag("meta:initial-creator")
264            self.get_meta_body().append(element)
265        element.text = creator

Set the first creator of the document.

Arguments:

creator -- str

Example::

>>> document.meta.set_initial_creator("Plato")
def get_creator(self) -> str | None:
267    def get_creator(self) -> str | None:
268        """Get the creator of the document.
269
270        Return: str (or None if inexistant)
271
272        Example::
273
274            >>> document.meta.get_creator()
275            Unknown
276        """
277        element = self.get_element("//dc:creator")
278        if element is None:
279            return None
280        return element.text

Get the creator of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_creator()
Unknown
def set_creator(self, creator: str) -> None:
282    def set_creator(self, creator: str) -> None:
283        """Set the creator of the document.
284
285        Arguments:
286
287            creator -- str
288
289        Example::
290
291            >>> document.meta.set_creator("Plato")
292        """
293        element = self.get_element("//dc:creator")
294        if element is None:
295            element = Element.from_tag("dc:creator")
296            self.get_meta_body().append(element)
297        element.text = creator

Set the creator of the document.

Arguments:

creator -- str

Example::

>>> document.meta.set_creator("Plato")
def get_keywords(self) -> str | None:
299    def get_keywords(self) -> str | None:
300        """Get the keywords of the document. Return the field as-is, without
301        any assumption on the keyword separator.
302
303        Return: str (or None if inexistant)
304        """
305        element = self.get_element("//meta:keyword")
306        if element is None:
307            return None
308        return element.text

Get the keywords of the document. Return the field as-is, without any assumption on the keyword separator.

Return: str (or None if inexistant)

def set_keywords(self, keywords: str) -> None:
310    def set_keywords(self, keywords: str) -> None:
311        """Set the keywords of the document. Although the name is plural, a
312        str string is required, so join your list first.
313
314        Arguments:
315
316            keywords -- str
317        """
318        element = self.get_element("//meta:keyword")
319        if element is None:
320            element = Element.from_tag("meta:keyword")
321            self.get_meta_body().append(element)
322        element.text = keywords

Set the keywords of the document. Although the name is plural, a str string is required, so join your list first.

Arguments:

keywords -- str
def get_editing_duration(self) -> datetime.timedelta | None:
324    def get_editing_duration(self) -> timedelta | None:
325        """Get the time the document was edited, as reported by the
326        generator.
327
328        Return: timedelta (or None if inexistant)
329        """
330        element = self.get_element("//meta:editing-duration")
331        if element is None:
332            return None
333        duration = element.text
334        return Duration.decode(duration)

Get the time the document was edited, as reported by the generator.

Return: timedelta (or None if inexistant)

def set_editing_duration(self, duration: datetime.timedelta) -> None:
336    def set_editing_duration(self, duration: timedelta) -> None:
337        """Set the time the document was edited.
338
339        Arguments:
340
341            duration -- timedelta
342        """
343        if not isinstance(duration, timedelta):
344            raise TypeError("duration must be a timedelta")
345        element = self.get_element("//meta:editing-duration")
346        if element is None:
347            element = Element.from_tag("meta:editing-duration")
348            self.get_meta_body().append(element)
349        element.text = Duration.encode(duration)

Set the time the document was edited.

Arguments:

duration -- timedelta
def get_editing_cycles(self) -> int | None:
351    def get_editing_cycles(self) -> int | None:
352        """Get the number of times the document was edited, as reported by
353        the generator.
354
355        Return: int (or None if inexistant)
356        """
357        element = self.get_element("//meta:editing-cycles")
358        if element is None:
359            return None
360        cycles = element.text
361        return int(cycles)

Get the number of times the document was edited, as reported by the generator.

Return: int (or None if inexistant)

def set_editing_cycles(self, cycles: int) -> None:
363    def set_editing_cycles(self, cycles: int) -> None:
364        """Set the number of times the document was edited.
365
366        Arguments:
367
368            cycles -- int
369        """
370        if not isinstance(cycles, int):
371            raise TypeError("cycles must be an int")
372        if cycles < 1:
373            raise ValueError("cycles must be a positive int")
374        element = self.get_element("//meta:editing-cycles")
375        if element is None:
376            element = Element.from_tag("meta:editing-cycles")
377            self.get_meta_body().append(element)
378        element.text = str(cycles)

Set the number of times the document was edited.

Arguments:

cycles -- int
def get_generator(self) -> str | None:
380    def get_generator(self) -> str | None:
381        """Get the signature of the software that generated this document.
382
383        Return: str (or None if inexistant)
384
385        Example::
386
387            >>> document.meta.get_generator()
388            KOffice/2.0.0
389        """
390        element = self.get_element("//meta:generator")
391        if element is None:
392            return None
393        return element.text

Get the signature of the software that generated this document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_generator()
KOffice/2.0.0
def set_generator(self, generator: str) -> None:
395    def set_generator(self, generator: str) -> None:
396        """Set the signature of the software that generated this document.
397
398        Arguments:
399
400            generator -- str
401
402        Example::
403
404            >>> document.meta.set_generator("Odfdo experiment")
405        """
406        element = self.get_element("//meta:generator")
407        if element is None:
408            element = Element.from_tag("meta:generator")
409            self.get_meta_body().append(element)
410        element.text = generator
411        self._generator_modified = True

Set the signature of the software that generated this document.

Arguments:

generator -- str

Example::

>>> document.meta.set_generator("Odfdo experiment")
def set_generator_default(self) -> None:
413    def set_generator_default(self) -> None:
414        """Set the signature of the software that generated this document
415        to ourself.
416
417        Example::
418
419            >>> document.meta.set_generator_default()
420        """
421        if not self._generator_modified:
422            self.set_generator(GENERATOR)

Set the signature of the software that generated this document to ourself.

Example::

>>> document.meta.set_generator_default()
def get_statistic(self) -> dict[str, int] | None:
424    def get_statistic(self) -> dict[str, int] | None:
425        """Get the statistic from the software that generated this document.
426
427        Return: dict (or None if inexistant)
428
429        Example::
430
431            >>> document.get_statistic():
432            {'meta:table-count': 1,
433             'meta:image-count': 2,
434             'meta:object-count': 3,
435             'meta:page-count': 4,
436             'meta:paragraph-count': 5,
437             'meta:word-count': 6,
438             'meta:character-count': 7}
439        """
440        element = self.get_element("//meta:document-statistic")
441        if element is None:
442            return None
443        statistic = {}
444        for key, value in element.attributes.items():
445            statistic[to_str(key)] = int(value)
446        return statistic

Get the statistic from the software that generated this document.

Return: dict (or None if inexistant)

Example::

>>> document.get_statistic():
{'meta:table-count': 1,
 'meta:image-count': 2,
 'meta:object-count': 3,
 'meta:page-count': 4,
 'meta:paragraph-count': 5,
 'meta:word-count': 6,
 'meta:character-count': 7}
def set_statistic(self, statistic: dict[str, int]) -> None:
448    def set_statistic(self, statistic: dict[str, int]) -> None:
449        """Set the statistic for the documents: number of words, paragraphs,
450        etc.
451
452        Arguments:
453
454            statistic -- dict
455
456        Example::
457
458            >>> statistic = {'meta:table-count': 1,
459                             'meta:image-count': 2,
460                             'meta:object-count': 3,
461                             'meta:page-count': 4,
462                             'meta:paragraph-count': 5,
463                             'meta:word-count': 6,
464                             'meta:character-count': 7}
465            >>> document.meta.set_statistic(statistic)
466        """
467        if not isinstance(statistic, dict):
468            raise TypeError("Statistic must be a dict")
469        element = self.get_element("//meta:document-statistic")
470        for key, value in statistic.items():
471            try:
472                ivalue = int(value)
473            except ValueError as e:
474                raise TypeError("Statistic value must be a int") from e
475            element.set_attribute(to_str(key), str(ivalue))

Set the statistic for the documents: number of words, paragraphs, etc.

Arguments:

statistic -- dict

Example::

>>> statistic = {'meta:table-count': 1,
                 'meta:image-count': 2,
                 'meta:object-count': 3,
                 'meta:page-count': 4,
                 'meta:paragraph-count': 5,
                 'meta:word-count': 6,
                 'meta:character-count': 7}
>>> document.meta.set_statistic(statistic)
def get_user_defined_metadata(self) -> dict[str, typing.Any]:
477    def get_user_defined_metadata(self) -> dict[str, Any]:
478        """Return a dict of str/value mapping.
479
480        Value types can be: Decimal, date, time, boolean or str.
481        """
482        result: dict[str, Any] = {}
483        for item in self.get_elements("//meta:user-defined"):
484            if not isinstance(item, Element):
485                continue
486            # Read the values
487            name = item.get_attribute_string("meta:name")
488            if name is None:
489                continue
490            value = self._get_meta_value(item)
491            result[name] = value
492        return result

Return a dict of str/value mapping.

Value types can be: Decimal, date, time, boolean or str.

def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, typing.Any] | None:
494    def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None:
495        """Return the content of the user defined metadata of that name.
496        Return None if no name matchs or a dic of fields.
497
498        Arguments:
499
500            name -- string, name (meta:name content)
501        """
502        result = {}
503        found = False
504        for item in self.get_elements("//meta:user-defined"):
505            if not isinstance(item, Element):
506                continue
507            # Read the values
508            name = item.get_attribute("meta:name")
509            if name == keyname:
510                found = True
511                break
512        if not found:
513            return None
514        result["name"] = name
515        value, value_type, text = self._get_meta_value(item, full=True)  # type: ignore
516        result["value"] = value
517        result["value_type"] = value_type
518        result["text"] = text
519        return result

Return the content of the user defined metadata of that name. Return None if no name matchs or a dic of fields.

Arguments:

name -- string, name (meta:name content)
def set_user_defined_metadata(self, name: str, value: Any) -> None:
521    def set_user_defined_metadata(self, name: str, value: Any) -> None:
522        if isinstance(value, bool):
523            value_type = "boolean"
524            value = "true" if value else "false"
525        elif isinstance(value, (int, float, Decimal)):
526            value_type = "float"
527            value = str(value)
528        elif isinstance(value, dtdate):
529            value_type = "date"
530            value = str(Date.encode(value))
531        elif isinstance(value, datetime):
532            value_type = "date"
533            value = str(DateTime.encode(value))
534        elif isinstance(value, str):
535            value_type = "string"
536        elif isinstance(value, timedelta):
537            value_type = "time"
538            value = str(Duration.encode(value))
539        else:
540            raise TypeError('unexpected type "%s" for value' % type(value))
541        # Already the same element ?
542        for metadata in self.get_elements("//meta:user-defined"):
543            if not isinstance(metadata, Element):
544                continue
545            if metadata.get_attribute("meta:name") == name:
546                break
547        else:
548            metadata = Element.from_tag("meta:user-defined")
549            metadata.set_attribute("meta:name", name)
550            self.get_meta_body().append(metadata)
551        metadata.set_attribute("meta:value-type", value_type)
552        metadata.text = value
NEXT_SIBLING = 2
class NamedRange(odfdo.Element):
2844class NamedRange(Element):
2845    """ODF Named Range "table:named-range". Identifies inside the spreadsheet
2846    a range of cells of a table by a name and the name of the table.
2847
2848    Name Ranges have the following attributes:
2849
2850        name -- name of the named range
2851
2852        table_name -- name of the table
2853
2854        start -- first cell of the named range, tuple (x, y)
2855
2856        end -- last cell of the named range, tuple (x, y)
2857
2858        crange -- range of the named range, tuple (x, y, z, t)
2859
2860        usage -- None or str, usage of the named range.
2861    """
2862
2863    _tag = "table:named-range"
2864
2865    def __init__(
2866        self,
2867        name: str | None = None,
2868        crange: str | tuple | list | None = None,
2869        table_name: str | None = None,
2870        usage: str | None = None,
2871        **kwargs: Any,
2872    ) -> None:
2873        """Create a Named Range element. 'name' must contains only letters, digits
2874           and '_', and must not be like a coordinate as 'A1'. 'table_name' must be
2875           a correct table name (no "'" or "/" in it).
2876
2877        Arguments:
2878
2879             name -- str, name of the named range
2880
2881             crange -- str or tuple of int, cell or area coordinate
2882
2883             table_name -- str, name of the table
2884
2885             usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2886        """
2887        super().__init__(**kwargs)
2888        self.usage = None
2889        if self._do_init:
2890            self.name = name or ""
2891            self.table_name = _table_name_check(table_name)
2892            self.set_range(crange or "")
2893            self.set_usage(usage)
2894        cell_range_address = self.get_attribute_string("table:cell-range-address") or ""
2895        if not cell_range_address:
2896            self.table_name = ""
2897            self.start = None
2898            self.end = None
2899            self.crange = None
2900            self.usage = None
2901            return
2902        self.usage = self.get_attribute("table:range-usable-as")
2903        name_range = cell_range_address.replace("$", "")
2904        name, crange = name_range.split(".", 1)
2905        if name.startswith("'") and name.endswith("'"):
2906            name = name[1:-1]
2907        self.table_name = name
2908        crange = crange.replace(".", "")
2909        self._set_range(crange)
2910
2911    def set_usage(self, usage: str | None = None) -> None:
2912        """Set the usage of the Named Range. Usage can be None (default) or one
2913        of :
2914            'print-range'
2915            'filter'
2916            'repeat-column'
2917            'repeat-row'
2918
2919        Arguments:
2920
2921            usage -- None or str
2922        """
2923        if usage is not None:
2924            usage = usage.strip().lower()
2925            if usage not in ("print-range", "filter", "repeat-column", "repeat-row"):
2926                usage = None
2927        if usage is None:
2928            with contextlib.suppress(KeyError):
2929                self.del_attribute("table:range-usable-as")
2930            self.usage = None
2931        else:
2932            self.set_attribute("table:range-usable-as", usage)
2933            self.usage = usage
2934
2935    @property
2936    def name(self) -> str | None:
2937        """Get / set the name of the table."""
2938        return self.get_attribute_string("table:name")
2939
2940    @name.setter
2941    def name(self, name: str) -> None:
2942        """Set the name of the Named Range. The name is mandatory, if a Named
2943        Range of the same name exists, it will be replaced. Name must contains
2944        only alphanumerics characters and '_', and can not be of a cell
2945        coordinates form like 'AB12'.
2946
2947        Arguments:
2948
2949            name -- str
2950        """
2951        name = name.strip()
2952        if not name:
2953            raise ValueError("Name required.")
2954        for x in name:
2955            if x in forbidden_in_named_range():
2956                raise ValueError(f"Character forbidden '{x}' ")
2957        step = ""
2958        for x in name:
2959            if x in string.ascii_letters and step in ("", "A"):
2960                step = "A"
2961                continue
2962            elif step in ("A", "A1") and x in string.digits:
2963                step = "A1"
2964                continue
2965            else:
2966                step = ""
2967                break
2968        if step == "A1":
2969            raise ValueError("Name of the type 'ABC123' is not allowed.")
2970        with contextlib.suppress(Exception):
2971            # we are not on an inserted in a document.
2972            body = self.document_body
2973            named_range = body.get_named_range(name)  # type: ignore
2974            if named_range:
2975                named_range.delete()
2976        self.set_attribute("table:name", name)
2977
2978    def set_table_name(self, name: str) -> None:
2979        """Set the name of the table of the Named Range. The name is mandatory.
2980
2981        Arguments:
2982
2983            name -- str
2984        """
2985        self.table_name = _table_name_check(name)
2986        self._update_attributes()
2987
2988    def _set_range(self, coord: tuple | list | str) -> None:
2989        digits = convert_coordinates(coord)
2990        if len(digits) == 4:
2991            x, y, z, t = digits
2992        else:
2993            x, y = digits
2994            z, t = digits
2995        self.start = x, y  # type: ignore
2996        self.end = z, t  # type: ignore
2997        self.crange = x, y, z, t  # type: ignore
2998
2999    def set_range(self, crange: str | tuple | list) -> None:
3000        """Set the range of the named range. Range can be either one cell
3001        (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric
3002        value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
3003
3004        Arguments:
3005
3006            crange -- str or tuple of int, cell or area coordinate
3007        """
3008        self._set_range(crange)
3009        self._update_attributes()
3010
3011    def _update_attributes(self) -> None:
3012        self.set_attribute("table:base-cell-address", self._make_base_cell_address())
3013        self.set_attribute("table:cell-range-address", self._make_cell_range_address())
3014
3015    def _make_base_cell_address(self) -> str:
3016        # assuming we got table_name and range
3017        if " " in self.table_name:
3018            name = f"'{self.table_name}'"
3019        else:
3020            name = self.table_name
3021        return f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}"  # type: ignore
3022
3023    def _make_cell_range_address(self) -> str:
3024        # assuming we got table_name and range
3025        if " " in self.table_name:
3026            name = f"'{self.table_name}'"
3027        else:
3028            name = self.table_name
3029        if self.start == self.end:
3030            return self._make_base_cell_address()
3031        return (
3032            f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}:"  # type: ignore
3033            f".${digit_to_alpha(self.end[0])}${self.end[1] + 1}"  # type: ignore
3034        )
3035
3036    def get_values(
3037        self,
3038        cell_type: str | None = None,
3039        complete: bool = True,
3040        get_type: bool = False,
3041        flat: bool = False,
3042    ) -> list:
3043        """Shortcut to retrieve the values of the cells of the named range. See
3044        table.get_values() for the arguments description and return format.
3045        """
3046        body = self.document_body
3047        if not body:
3048            raise ValueError("Table is not inside a document.")
3049        table = body.get_table(name=self.table_name)
3050        if table is None:
3051            raise ValueError
3052        return table.get_values(self.crange, cell_type, complete, get_type, flat)  # type: ignore
3053
3054    def get_value(self, get_type: bool = False) -> Any:
3055        """Shortcut to retrieve the value of the first cell of the named range.
3056        See table.get_value() for the arguments description and return format.
3057        """
3058        body = self.document_body
3059        if not body:
3060            raise ValueError("Table is not inside a document.")
3061        table = body.get_table(name=self.table_name)
3062        if table is None:
3063            raise ValueError
3064        return table.get_value(self.start, get_type)  # type: ignore
3065
3066    def set_values(
3067        self,
3068        values: list,
3069        style: str | None = None,
3070        cell_type: str | None = None,
3071        currency: str | None = None,
3072    ) -> None:
3073        """Shortcut to set the values of the cells of the named range.
3074        See table.set_values() for the arguments description.
3075        """
3076        body = self.document_body
3077        if not body:
3078            raise ValueError("Table is not inside a document.")
3079        table = body.get_table(name=self.table_name)
3080        if table is None:
3081            raise ValueError
3082        table.set_values(  # type: ignore
3083            values,
3084            coord=self.crange,
3085            style=style,
3086            cell_type=cell_type,
3087            currency=currency,
3088        )
3089
3090    def set_value(
3091        self,
3092        value: Any,
3093        cell_type: str | None = None,
3094        currency: str | None = None,
3095        style: str | None = None,
3096    ) -> None:
3097        """Shortcut to set the value of the first cell of the named range.
3098        See table.set_value() for the arguments description.
3099        """
3100        body = self.document_body
3101        if not body:
3102            raise ValueError("Table is not inside a document.")
3103        table = body.get_table(name=self.table_name)
3104        if table is None:
3105            raise ValueError
3106        table.set_value(  # type: ignore
3107            coord=self.start,
3108            value=value,
3109            cell_type=cell_type,
3110            currency=currency,
3111            style=style,
3112        )

ODF Named Range "table:named-range". Identifies inside the spreadsheet a range of cells of a table by a name and the name of the table.

Name Ranges have the following attributes:

name -- name of the named range

table_name -- name of the table

start -- first cell of the named range, tuple (x, y)

end -- last cell of the named range, tuple (x, y)

crange -- range of the named range, tuple (x, y, z, t)

usage -- None or str, usage of the named range.
NamedRange( name: str | None = None, crange: str | tuple | list | None = None, table_name: str | None = None, usage: str | None = None, **kwargs: Any)
2865    def __init__(
2866        self,
2867        name: str | None = None,
2868        crange: str | tuple | list | None = None,
2869        table_name: str | None = None,
2870        usage: str | None = None,
2871        **kwargs: Any,
2872    ) -> None:
2873        """Create a Named Range element. 'name' must contains only letters, digits
2874           and '_', and must not be like a coordinate as 'A1'. 'table_name' must be
2875           a correct table name (no "'" or "/" in it).
2876
2877        Arguments:
2878
2879             name -- str, name of the named range
2880
2881             crange -- str or tuple of int, cell or area coordinate
2882
2883             table_name -- str, name of the table
2884
2885             usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2886        """
2887        super().__init__(**kwargs)
2888        self.usage = None
2889        if self._do_init:
2890            self.name = name or ""
2891            self.table_name = _table_name_check(table_name)
2892            self.set_range(crange or "")
2893            self.set_usage(usage)
2894        cell_range_address = self.get_attribute_string("table:cell-range-address") or ""
2895        if not cell_range_address:
2896            self.table_name = ""
2897            self.start = None
2898            self.end = None
2899            self.crange = None
2900            self.usage = None
2901            return
2902        self.usage = self.get_attribute("table:range-usable-as")
2903        name_range = cell_range_address.replace("$", "")
2904        name, crange = name_range.split(".", 1)
2905        if name.startswith("'") and name.endswith("'"):
2906            name = name[1:-1]
2907        self.table_name = name
2908        crange = crange.replace(".", "")
2909        self._set_range(crange)

Create a Named Range element. 'name' must contains only letters, digits and '_', and must not be like a coordinate as 'A1'. 'table_name' must be a correct table name (no "'" or "/" in it).

Arguments:

 name -- str, name of the named range

 crange -- str or tuple of int, cell or area coordinate

 table_name -- str, name of the table

 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
usage
table_name
def set_usage(self, usage: str | None = None) -> None:
2911    def set_usage(self, usage: str | None = None) -> None:
2912        """Set the usage of the Named Range. Usage can be None (default) or one
2913        of :
2914            'print-range'
2915            'filter'
2916            'repeat-column'
2917            'repeat-row'
2918
2919        Arguments:
2920
2921            usage -- None or str
2922        """
2923        if usage is not None:
2924            usage = usage.strip().lower()
2925            if usage not in ("print-range", "filter", "repeat-column", "repeat-row"):
2926                usage = None
2927        if usage is None:
2928            with contextlib.suppress(KeyError):
2929                self.del_attribute("table:range-usable-as")
2930            self.usage = None
2931        else:
2932            self.set_attribute("table:range-usable-as", usage)
2933            self.usage = usage

Set the usage of the Named Range. Usage can be None (default) or one of : 'print-range' 'filter' 'repeat-column' 'repeat-row'

Arguments:

usage -- None or str
name: str | None
2935    @property
2936    def name(self) -> str | None:
2937        """Get / set the name of the table."""
2938        return self.get_attribute_string("table:name")

Get / set the name of the table.

def set_table_name(self, name: str) -> None:
2978    def set_table_name(self, name: str) -> None:
2979        """Set the name of the table of the Named Range. The name is mandatory.
2980
2981        Arguments:
2982
2983            name -- str
2984        """
2985        self.table_name = _table_name_check(name)
2986        self._update_attributes()

Set the name of the table of the Named Range. The name is mandatory.

Arguments:

name -- str
def set_range(self, crange: str | tuple | list) -> None:
2999    def set_range(self, crange: str | tuple | list) -> None:
3000        """Set the range of the named range. Range can be either one cell
3001        (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric
3002        value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
3003
3004        Arguments:
3005
3006            crange -- str or tuple of int, cell or area coordinate
3007        """
3008        self._set_range(crange)
3009        self._update_attributes()

Set the range of the named range. Range can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).

Arguments:

crange -- str or tuple of int, cell or area coordinate
def get_values( self, cell_type: str | None = None, complete: bool = True, get_type: bool = False, flat: bool = False) -> list:
3036    def get_values(
3037        self,
3038        cell_type: str | None = None,
3039        complete: bool = True,
3040        get_type: bool = False,
3041        flat: bool = False,
3042    ) -> list:
3043        """Shortcut to retrieve the values of the cells of the named range. See
3044        table.get_values() for the arguments description and return format.
3045        """
3046        body = self.document_body
3047        if not body:
3048            raise ValueError("Table is not inside a document.")
3049        table = body.get_table(name=self.table_name)
3050        if table is None:
3051            raise ValueError
3052        return table.get_values(self.crange, cell_type, complete, get_type, flat)  # type: ignore

Shortcut to retrieve the values of the cells of the named range. See table.get_values() for the arguments description and return format.

def get_value(self, get_type: bool = False) -> Any:
3054    def get_value(self, get_type: bool = False) -> Any:
3055        """Shortcut to retrieve the value of the first cell of the named range.
3056        See table.get_value() for the arguments description and return format.
3057        """
3058        body = self.document_body
3059        if not body:
3060            raise ValueError("Table is not inside a document.")
3061        table = body.get_table(name=self.table_name)
3062        if table is None:
3063            raise ValueError
3064        return table.get_value(self.start, get_type)  # type: ignore

Shortcut to retrieve the value of the first cell of the named range. See table.get_value() for the arguments description and return format.

def set_values( self, values: list, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
3066    def set_values(
3067        self,
3068        values: list,
3069        style: str | None = None,
3070        cell_type: str | None = None,
3071        currency: str | None = None,
3072    ) -> None:
3073        """Shortcut to set the values of the cells of the named range.
3074        See table.set_values() for the arguments description.
3075        """
3076        body = self.document_body
3077        if not body:
3078            raise ValueError("Table is not inside a document.")
3079        table = body.get_table(name=self.table_name)
3080        if table is None:
3081            raise ValueError
3082        table.set_values(  # type: ignore
3083            values,
3084            coord=self.crange,
3085            style=style,
3086            cell_type=cell_type,
3087            currency=currency,
3088        )

Shortcut to set the values of the cells of the named range. See table.set_values() for the arguments description.

def set_value( self, value: Any, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
3090    def set_value(
3091        self,
3092        value: Any,
3093        cell_type: str | None = None,
3094        currency: str | None = None,
3095        style: str | None = None,
3096    ) -> None:
3097        """Shortcut to set the value of the first cell of the named range.
3098        See table.set_value() for the arguments description.
3099        """
3100        body = self.document_body
3101        if not body:
3102            raise ValueError("Table is not inside a document.")
3103        table = body.get_table(name=self.table_name)
3104        if table is None:
3105            raise ValueError
3106        table.set_value(  # type: ignore
3107            coord=self.start,
3108            value=value,
3109            cell_type=cell_type,
3110            currency=currency,
3111            style=style,
3112        )

Shortcut to set the value of the first cell of the named range. See table.set_value() for the arguments description.

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Note(odfdo.Element):
 57class Note(Element):
 58    """Either a footnote or a endnote element with the given text,
 59    optionally referencing it using the given note_id.
 60
 61    Arguments:
 62
 63        note_class -- 'footnote' or 'endnote'
 64
 65        note_id -- str
 66
 67        citation -- str
 68
 69        body -- str or Element
 70    """
 71
 72    _tag = "text:note"
 73    _properties = (
 74        PropDef("note_class", "text:note-class"),
 75        PropDef("note_id", "text:id"),
 76    )
 77
 78    def __init__(
 79        self,
 80        note_class: str = "footnote",
 81        note_id: str | None = None,
 82        citation: str | None = None,
 83        body: str | None = None,
 84        **kwargs: Any,
 85    ) -> None:
 86        super().__init__(**kwargs)
 87        if self._do_init:
 88            self.insert(Element.from_tag("text:note-body"), position=0)
 89            self.insert(Element.from_tag("text:note-citation"), position=0)
 90            self.note_class = note_class
 91            if note_id is not None:
 92                self.note_id = note_id
 93            if citation is not None:
 94                self.citation = citation
 95            if body is not None:
 96                self.note_body = body
 97
 98    @property
 99    def citation(self) -> str:
100        note_citation = self.get_element("text:note-citation")
101        if note_citation:
102            return note_citation.text
103        return ""
104
105    @citation.setter
106    def citation(self, text: str | None) -> None:
107        note_citation = self.get_element("text:note-citation")
108        if note_citation:
109            note_citation.text = text  # type:ignore
110
111    @property
112    def note_body(self) -> str:
113        note_body = self.get_element("text:note-body")
114        if note_body:
115            return note_body.text_content
116        return ""
117
118    @note_body.setter
119    def note_body(self, text_or_element: Element | str | None) -> None:
120        note_body = self.get_element("text:note-body")
121        if not note_body:
122            return None
123        if text_or_element is None:
124            note_body.text_content = ""
125        elif isinstance(text_or_element, str):
126            note_body.text_content = text_or_element
127        elif isinstance(text_or_element, Element):
128            note_body.clear()
129            note_body.append(text_or_element)
130        else:
131            raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"')
132
133    def check_validity(self) -> None:
134        if not self.note_class:
135            raise ValueError('Note class must be "footnote" or "endnote"')
136        if not self.note_id:
137            raise ValueError("Note must have an id")
138        if not self.citation:
139            raise ValueError("Note must have a citation")
140        if not self.note_body:
141            pass

Either a footnote or a endnote element with the given text, optionally referencing it using the given note_id.

Arguments:

note_class -- 'footnote' or 'endnote'

note_id -- str

citation -- str

body -- str or Element
Note( note_class: str = 'footnote', note_id: str | None = None, citation: str | None = None, body: str | None = None, **kwargs: Any)
78    def __init__(
79        self,
80        note_class: str = "footnote",
81        note_id: str | None = None,
82        citation: str | None = None,
83        body: str | None = None,
84        **kwargs: Any,
85    ) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.insert(Element.from_tag("text:note-body"), position=0)
89            self.insert(Element.from_tag("text:note-citation"), position=0)
90            self.note_class = note_class
91            if note_id is not None:
92                self.note_id = note_id
93            if citation is not None:
94                self.citation = citation
95            if body is not None:
96                self.note_body = body
citation: str
 98    @property
 99    def citation(self) -> str:
100        note_citation = self.get_element("text:note-citation")
101        if note_citation:
102            return note_citation.text
103        return ""
note_body: str
111    @property
112    def note_body(self) -> str:
113        note_body = self.get_element("text:note-body")
114        if note_body:
115            return note_body.text_content
116        return ""
def check_validity(self) -> None:
133    def check_validity(self) -> None:
134        if not self.note_class:
135            raise ValueError('Note class must be "footnote" or "endnote"')
136        if not self.note_id:
137            raise ValueError("Note must have an id")
138        if not self.citation:
139            raise ValueError("Note must have a citation")
140        if not self.note_body:
141            pass
note_class: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
note_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
PREV_SIBLING = 3
class Paragraph(odfdo.paragraph_base.ParagraphBase):
147class Paragraph(ParagraphBase):
148    """Specialised element for paragraphs "text:p". The "text:p" element
149    represents a paragraph, which is the basic unit of text in an OpenDocument
150    file.
151    """
152
153    _tag = "text:p"
154
155    def __init__(
156        self,
157        text_or_element: str | Element | None = None,
158        style: str | None = None,
159        **kwargs: Any,
160    ):
161        """Create a paragraph element of the given style containing the optional
162        given text.
163
164        Arguments:
165
166            text -- str or Element
167
168            style -- str
169        """
170        super().__init__(**kwargs)
171        if self._do_init:
172            if isinstance(text_or_element, Element):
173                self.append(text_or_element)
174            else:
175                self.text = text_or_element  # type:ignore
176            if style is not None:
177                self.style = style
178
179    def insert_note(
180        self,
181        note_element: Note | None = None,
182        after: str | Element | None = None,
183        note_class: str = "footnote",
184        note_id: str | None = None,
185        citation: str | None = None,
186        body: str | None = None,
187    ) -> None:
188        if note_element is None:
189            note_element = Note(
190                note_class=note_class, note_id=note_id, citation=citation, body=body
191            )
192        else:
193            # XXX clone or modify the argument?
194            if note_class:
195                note_element.note_class = note_class
196            if note_id:
197                note_element.note_id = note_id
198            if citation:
199                note_element.citation = citation
200            if body:
201                note_element.note_body = body
202        note_element.check_validity()
203        if isinstance(after, str):
204            self._insert(note_element, after=after, main_text=True)
205        elif isinstance(after, Element):
206            after.insert(note_element, FIRST_CHILD)
207        else:
208            self.insert(note_element, FIRST_CHILD)
209
210    def insert_annotation(  # noqa: C901
211        self,
212        annotation_element: Annotation | None = None,
213        before: str | None = None,
214        after: str | Element | None = None,
215        position: int | tuple = 0,
216        content: str | Element | None = None,
217        body: str | None = None,
218        creator: str | None = None,
219        date: datetime | None = None,
220    ) -> Annotation:
221        """Insert an annotation, at the position defined by the regex (before,
222        after, content) or by positionnal argument (position). If content is
223        provided, the annotation covers the full content regex. Else, the
224        annotation is positionned either 'before' or 'after' provided regex.
225
226        If content is an odf element (ie: paragraph, span, ...), the full inner
227        content is covered by the annotation (of the position just after if
228        content is a single empty tag).
229
230        If content/before or after exists (regex) and return a group of matching
231        positions, the position value is the index of matching place to use.
232
233        annotation_element can contain a previously created annotation, else
234        the annotation is created from the body, creator and optional date
235        (current date by default).
236
237        Arguments:
238
239            annotation_element -- Annotation or None
240
241            before -- str regular expression or None
242
243            after -- str regular expression or Element or None
244
245            content -- str regular expression or None, or Element
246
247            position -- int or tuple of int
248
249            body -- str or Element
250
251            creator -- str
252
253            date -- datetime
254        """
255
256        if annotation_element is None:
257            annotation_element = Annotation(
258                text_or_element=body, creator=creator, date=date, parent=self
259            )
260        else:
261            # XXX clone or modify the argument?
262            if body:
263                annotation_element.note_body = body
264            if creator:
265                annotation_element.dc_creator = creator
266            if date:
267                annotation_element.dc_date = date
268        annotation_element.check_validity()
269
270        # special case: content is an odf element (ie: a paragraph)
271        if isinstance(content, Element):
272            if content.is_empty():
273                content.insert(annotation_element, xmlposition=NEXT_SIBLING)
274                return annotation_element
275            content.insert(annotation_element, start=True)
276            annotation_end = AnnotationEnd(annotation_element)
277            content.append(annotation_end)
278            return annotation_element
279
280        # special case
281        if isinstance(after, Element):
282            after.insert(annotation_element, FIRST_CHILD)
283            return annotation_element
284
285        # With "content" => automatically insert a "start" and an "end"
286        # bookmark
287        if (
288            before is None
289            and after is None
290            and content is not None
291            and isinstance(position, int)
292        ):
293            # Start tag
294            self._insert(
295                annotation_element, before=content, position=position, main_text=True
296            )
297            # End tag
298            annotation_end = AnnotationEnd(annotation_element)
299            self._insert(
300                annotation_end, after=content, position=position, main_text=True
301            )
302            return annotation_element
303
304        # With "(int, int)" =>  automatically insert a "start" and an "end"
305        # bookmark
306        if (
307            before is None
308            and after is None
309            and content is None
310            and isinstance(position, tuple)
311        ):
312            # Start
313            self._insert(annotation_element, position=position[0], main_text=True)
314            # End
315            annotation_end = AnnotationEnd(annotation_element)
316            self._insert(annotation_end, position=position[1], main_text=True)
317            return annotation_element
318
319        # Without "content" nor "position"
320        if content is not None or not isinstance(position, int):
321            raise ValueError("Bad arguments")
322
323        # Insert
324        self._insert(
325            annotation_element,
326            before=before,
327            after=after,
328            position=position,
329            main_text=True,
330        )
331        return annotation_element
332
333    def insert_annotation_end(
334        self,
335        annotation_element: Annotation,
336        before: str | None = None,
337        after: str | None = None,
338        position: int = 0,
339    ) -> AnnotationEnd:
340        """Insert an annotation end tag for an existing annotation. If some end
341        tag already exists, replace it. Annotation end tag is set at the
342        position defined by the regex (before or after).
343
344        If content/before or after (regex) returns a group of matching
345        positions, the position value is the index of matching place to use.
346
347        Arguments:
348
349            annotation_element -- Annotation (mandatory)
350
351            before -- str regular expression or None
352
353            after -- str regular expression or None
354
355            position -- int
356        """
357
358        if annotation_element is None:
359            raise ValueError
360        if not isinstance(annotation_element, Annotation):
361            raise TypeError("Not a <office:annotation> Annotation")
362
363        # remove existing end tag
364        name = annotation_element.name
365        existing_end_tag = self.get_annotation_end(name=name)
366        if existing_end_tag:
367            existing_end_tag.delete()
368
369        # create the end tag
370        end_tag = AnnotationEnd(annotation_element)
371
372        # Insert
373        self._insert(
374            end_tag, before=before, after=after, position=position, main_text=True
375        )
376        return end_tag
377
378    def set_reference_mark(
379        self,
380        name: str,
381        before: str | None = None,
382        after: str | None = None,
383        position: int = 0,
384        content: str | Element | None = None,
385    ) -> Element:
386        """Insert a reference mark, at the position defined by the regex
387        (before, after, content) or by positionnal argument (position). If
388        content is provided, the annotation covers the full range content regex
389        (instances of ReferenceMarkStart and ReferenceMarkEnd are
390        created). Else, an instance of ReferenceMark is positionned either
391        'before' or 'after' provided regex.
392
393        If content is an ODF Element (ie: Paragraph, Span, ...), the full inner
394        content is referenced (of the position just after if content is a single
395        empty tag).
396
397        If content/before or after exists (regex) and return a group of matching
398        positions, the position value is the index of matching place to use.
399
400        Name is mandatory and shall be unique in the document for the preference
401        mark range.
402
403        Arguments:
404
405            name -- str
406
407            before -- str regular expression or None
408
409            after -- str regular expression or None,
410
411            content -- str regular expression or None, or Element
412
413            position -- int or tuple of int
414
415        Return: the created ReferenceMark or ReferenceMarkStart
416        """
417        # special case: content is an odf element (ie: a paragraph)
418        if isinstance(content, Element):
419            if content.is_empty():
420                reference = ReferenceMark(name)
421                content.insert(reference, xmlposition=NEXT_SIBLING)
422                return reference
423            reference_start = ReferenceMarkStart(name)
424            content.insert(reference_start, start=True)
425            reference_end = ReferenceMarkEnd(name)
426            content.append(reference_end)
427            return reference_start
428
429        # With "content" => automatically insert a "start" and an "end"
430        # reference
431        if (
432            before is None
433            and after is None
434            and content is not None
435            and isinstance(position, int)
436        ):
437            # Start tag
438            reference_start = ReferenceMarkStart(name)
439            self._insert(
440                reference_start, before=content, position=position, main_text=True
441            )
442            # End tag
443            reference_end = ReferenceMarkEnd(name)
444            self._insert(
445                reference_end, after=content, position=position, main_text=True
446            )
447            return reference_start
448
449        # With "(int, int)" =>  automatically insert a "start" and an "end"
450        if (
451            before is None
452            and after is None
453            and content is None
454            and isinstance(position, tuple)
455        ):
456            # Start
457            reference_start = ReferenceMarkStart(name)
458            self._insert(reference_start, position=position[0], main_text=True)
459            # End
460            reference_end = ReferenceMarkEnd(name)
461            self._insert(reference_end, position=position[1], main_text=True)
462            return reference_start
463
464        # Without "content" nor "position"
465        if content is not None or not isinstance(position, int):
466            raise ValueError("bad arguments")
467
468        # Insert a positional reference mark
469        reference = ReferenceMark(name)
470        self._insert(
471            reference,
472            before=before,
473            after=after,
474            position=position,
475            main_text=True,
476        )
477        return reference
478
479    def set_reference_mark_end(
480        self,
481        reference_mark: Element,
482        before: str | None = None,
483        after: str | None = None,
484        position: int = 0,
485    ) -> ReferenceMarkEnd:
486        """Insert/move a ReferenceMarkEnd for an existing reference mark. If
487        some end tag already exists, replace it. Reference tag is set at the
488        position defined by the regex (before or after).
489
490        If content/before or after (regex) returns a group of matching
491        positions, the position value is the index of matching place to use.
492
493        Arguments:
494
495            reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
496
497            before -- str regular expression or None
498
499            after -- str regular expression or None
500
501            position -- int
502        """
503        if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)):
504            raise TypeError("Not a ReferenceMark or ReferenceMarkStart")
505        name = reference_mark.name
506        if isinstance(reference_mark, ReferenceMark):
507            # change it to a range reference:
508            reference_mark.tag = ReferenceMarkStart._tag
509
510        existing_end_tag = self.get_reference_mark_end(name=name)
511        if existing_end_tag:
512            existing_end_tag.delete()
513
514        # create the end tag
515        end_tag = ReferenceMarkEnd(name)
516
517        # Insert
518        self._insert(
519            end_tag, before=before, after=after, position=position, main_text=True
520        )
521        return end_tag
522
523    def insert_variable(self, variable_element: Element, after: str | None) -> None:
524        self._insert(variable_element, after=after, main_text=True)
525
526    @_by_regex_offset
527    def set_span(
528        self,
529        match: str,
530        tail: str,
531        style: str,
532        regex: str | None = None,
533        offset: int | None = None,
534        length: int = 0,
535    ) -> Span:
536        """
537        set_span(style, regex=None, offset=None, length=0)
538        Apply the given style to text content matching the regex OR the
539        positional arguments offset and length.
540
541        (match, tail: provided by regex decorator)
542
543        Arguments:
544
545            style -- str
546
547            regex -- str regular expression
548
549            offset -- int
550
551            length -- int
552        """
553        span = Span(match, style=style)
554        span.tail = tail
555        return span
556
557    def remove_spans(self, keep_heading: bool = True) -> Element | list:
558        """Send back a copy of the element, without span styles.
559        If keep_heading is True (default), the first level heading style is left
560        unchanged.
561        """
562        strip = (Span._tag,)
563        if keep_heading:
564            protect = ("text:h",)
565        else:
566            protect = None
567        return self.strip_tags(strip=strip, protect=protect)
568
569    def remove_span(self, spans: Element | list[Element]) -> Element | list:
570        """Send back a copy of the element, the spans (not a clone) removed.
571
572        Arguments:
573
574            spans -- Element or list of Element
575        """
576        return self.strip_elements(spans)
577
578    @_by_regex_offset
579    def set_link(
580        self,
581        match: str,
582        tail: str,
583        url: str,
584        regex: str | None = None,
585        offset: int | None = None,
586        length: int = 0,
587    ) -> Element:
588        """
589        set_link(url, regex=None, offset=None, length=0)
590        Make a link to the provided url from text content matching the regex
591        OR the positional arguments offset and length.
592
593        (match, tail: provided by regex decorator)
594
595        Arguments:
596
597            url -- str
598
599            regex -- str regular expression
600
601            offset -- int
602
603            length -- int
604        """
605        link = Link(url, text=match)
606        link.tail = tail
607        return link
608
609    def remove_links(self) -> Element | list:
610        """Send back a copy of the element, without links tags."""
611        strip = (Link._tag,)
612        return self.strip_tags(strip=strip)
613
614    def remove_link(self, links: Link | list[Link]) -> Element | list:
615        """Send back a copy of the element (not a clone), with the sub links
616           removed.
617
618        Arguments:
619
620            links -- Link or list of Link
621        """
622        return self.strip_elements(links)
623
624    def insert_reference(
625        self,
626        name: str,
627        ref_format: str = "",
628        before: str | None = None,
629        after: str | Element | None = None,
630        position: int = 0,
631        display: str | None = None,
632    ) -> None:
633        """Create and insert a reference to a content marked by a reference
634        mark. The Reference element ("text:reference-ref") represents a
635        field that references a "text:reference-mark-start" or
636        "text:reference-mark" element. Its "text:reference-format" attribute
637        specifies what is displayed from the referenced element. Default is
638        'page'. Actual content is not automatically updated except for the 'text'
639        format.
640
641        name is mandatory and should represent an existing reference mark of the
642        document.
643
644        ref_format is the argument for format reference (default is 'page').
645
646        The reference is inserted the position defined by the regex (before /
647        after), or by positionnal argument (position). If 'display' is provided,
648        it will be used as the text value for the reference.
649
650        If after is an ODF Element, the reference is inserted as first child of
651        this element.
652
653        Arguments:
654
655            name -- str
656
657            ref_format -- one of : 'chapter', 'direction', 'page', 'text',
658                                    'caption', 'category-and-value', 'value',
659                                    'number', 'number-all-superior',
660                                    'number-no-superior'
661
662            before -- str regular expression or None
663
664            after -- str regular expression or odf element or None
665
666            position -- int
667
668            display -- str or None
669        """
670        reference = Reference(name, ref_format)
671        if display is None and ref_format == "text":
672            # get reference content
673            body = self.document_body
674            if not body:
675                body = self.root
676            mark = body.get_reference_mark(name=name)
677            if mark:
678                display = mark.referenced_text  # type: ignore
679        if not display:
680            display = " "
681        reference.text = display
682        if isinstance(after, Element):
683            after.insert(reference, FIRST_CHILD)
684        else:
685            self._insert(
686                reference, before=before, after=after, position=position, main_text=True
687            )
688
689    def set_bookmark(
690        self,
691        name: str,
692        before: str | None = None,
693        after: str | None = None,
694        position: int | tuple = 0,
695        role: str | None = None,
696        content: str | None = None,
697    ) -> Element | tuple[Element, Element]:
698        """Insert a bookmark before or after the characters in the text which
699        match the regex before/after. When the regex matches more of one part
700        of the text, position can be set to choose which part must be used.
701        If before and after are None, we use only position that is the number
702        of characters.
703
704        So, by default, this function inserts a bookmark before the first
705        character of the content. Role can be None, "start" or "end", we
706        insert respectively a position bookmark a bookmark-start or a
707        bookmark-end.
708
709        If content is not None these 2 calls are equivalent:
710
711          paragraph.set_bookmark("bookmark", content="xyz")
712
713        and:
714
715          paragraph.set_bookmark("bookmark", before="xyz", role="start")
716          paragraph.set_bookmark("bookmark", after="xyz", role="end")
717
718
719        If position is a 2-tuple, these 2 calls are equivalent:
720
721          paragraph.set_bookmark("bookmark", position=(10, 20))
722
723        and:
724
725          paragraph.set_bookmark("bookmark", position=10, role="start")
726          paragraph.set_bookmark("bookmark", position=20, role="end")
727
728
729        Arguments:
730
731            name -- str
732
733            before -- str regex
734
735            after -- str regex
736
737            position -- int or (int, int)
738
739            role -- None, "start" or "end"
740
741            content -- str regex
742        """
743        # With "content" => automatically insert a "start" and an "end"
744        # bookmark
745        if (
746            before is None
747            and after is None
748            and role is None
749            and content is not None
750            and isinstance(position, int)
751        ):
752            # Start
753            start = BookmarkStart(name)
754            self._insert(start, before=content, position=position, main_text=True)
755            # End
756            end = BookmarkEnd(name)
757            self._insert(end, after=content, position=position, main_text=True)
758            return start, end
759
760        # With "(int, int)" =>  automatically insert a "start" and an "end"
761        # bookmark
762        if (
763            before is None
764            and after is None
765            and role is None
766            and content is None
767            and isinstance(position, tuple)
768        ):
769            # Start
770            start = BookmarkStart(name)
771            self._insert(start, position=position[0], main_text=True)
772            # End
773            end = BookmarkEnd(name)
774            self._insert(end, position=position[1], main_text=True)
775            return start, end
776
777        # Without "content" nor "position"
778        if content is not None or not isinstance(position, int):
779            raise ValueError("bad arguments")
780
781        # Role
782        if role is None:
783            bookmark: Element = Bookmark(name)
784        elif role == "start":
785            bookmark = BookmarkStart(name)
786        elif role == "end":
787            bookmark = BookmarkEnd(name)
788        else:
789            raise ValueError("bad arguments")
790
791        # Insert
792        self._insert(
793            bookmark, before=before, after=after, position=position, main_text=True
794        )
795
796        return bookmark

Specialised element for paragraphs "text:p". The "text:p" element represents a paragraph, which is the basic unit of text in an OpenDocument file.

Paragraph( text_or_element: str | Element | None = None, style: str | None = None, **kwargs: Any)
155    def __init__(
156        self,
157        text_or_element: str | Element | None = None,
158        style: str | None = None,
159        **kwargs: Any,
160    ):
161        """Create a paragraph element of the given style containing the optional
162        given text.
163
164        Arguments:
165
166            text -- str or Element
167
168            style -- str
169        """
170        super().__init__(**kwargs)
171        if self._do_init:
172            if isinstance(text_or_element, Element):
173                self.append(text_or_element)
174            else:
175                self.text = text_or_element  # type:ignore
176            if style is not None:
177                self.style = style

Create a paragraph element of the given style containing the optional given text.

Arguments:

text -- str or Element

style -- str
def insert_note( self, note_element: Note | None = None, after: str | Element | None = None, note_class: str = 'footnote', note_id: str | None = None, citation: str | None = None, body: str | None = None) -> None:
179    def insert_note(
180        self,
181        note_element: Note | None = None,
182        after: str | Element | None = None,
183        note_class: str = "footnote",
184        note_id: str | None = None,
185        citation: str | None = None,
186        body: str | None = None,
187    ) -> None:
188        if note_element is None:
189            note_element = Note(
190                note_class=note_class, note_id=note_id, citation=citation, body=body
191            )
192        else:
193            # XXX clone or modify the argument?
194            if note_class:
195                note_element.note_class = note_class
196            if note_id:
197                note_element.note_id = note_id
198            if citation:
199                note_element.citation = citation
200            if body:
201                note_element.note_body = body
202        note_element.check_validity()
203        if isinstance(after, str):
204            self._insert(note_element, after=after, main_text=True)
205        elif isinstance(after, Element):
206            after.insert(note_element, FIRST_CHILD)
207        else:
208            self.insert(note_element, FIRST_CHILD)
def insert_annotation( self, annotation_element: Annotation | None = None, before: str | None = None, after: str | Element | None = None, position: int | tuple = 0, content: str | Element | None = None, body: str | None = None, creator: str | None = None, date: datetime.datetime | None = None) -> Annotation:
210    def insert_annotation(  # noqa: C901
211        self,
212        annotation_element: Annotation | None = None,
213        before: str | None = None,
214        after: str | Element | None = None,
215        position: int | tuple = 0,
216        content: str | Element | None = None,
217        body: str | None = None,
218        creator: str | None = None,
219        date: datetime | None = None,
220    ) -> Annotation:
221        """Insert an annotation, at the position defined by the regex (before,
222        after, content) or by positionnal argument (position). If content is
223        provided, the annotation covers the full content regex. Else, the
224        annotation is positionned either 'before' or 'after' provided regex.
225
226        If content is an odf element (ie: paragraph, span, ...), the full inner
227        content is covered by the annotation (of the position just after if
228        content is a single empty tag).
229
230        If content/before or after exists (regex) and return a group of matching
231        positions, the position value is the index of matching place to use.
232
233        annotation_element can contain a previously created annotation, else
234        the annotation is created from the body, creator and optional date
235        (current date by default).
236
237        Arguments:
238
239            annotation_element -- Annotation or None
240
241            before -- str regular expression or None
242
243            after -- str regular expression or Element or None
244
245            content -- str regular expression or None, or Element
246
247            position -- int or tuple of int
248
249            body -- str or Element
250
251            creator -- str
252
253            date -- datetime
254        """
255
256        if annotation_element is None:
257            annotation_element = Annotation(
258                text_or_element=body, creator=creator, date=date, parent=self
259            )
260        else:
261            # XXX clone or modify the argument?
262            if body:
263                annotation_element.note_body = body
264            if creator:
265                annotation_element.dc_creator = creator
266            if date:
267                annotation_element.dc_date = date
268        annotation_element.check_validity()
269
270        # special case: content is an odf element (ie: a paragraph)
271        if isinstance(content, Element):
272            if content.is_empty():
273                content.insert(annotation_element, xmlposition=NEXT_SIBLING)
274                return annotation_element
275            content.insert(annotation_element, start=True)
276            annotation_end = AnnotationEnd(annotation_element)
277            content.append(annotation_end)
278            return annotation_element
279
280        # special case
281        if isinstance(after, Element):
282            after.insert(annotation_element, FIRST_CHILD)
283            return annotation_element
284
285        # With "content" => automatically insert a "start" and an "end"
286        # bookmark
287        if (
288            before is None
289            and after is None
290            and content is not None
291            and isinstance(position, int)
292        ):
293            # Start tag
294            self._insert(
295                annotation_element, before=content, position=position, main_text=True
296            )
297            # End tag
298            annotation_end = AnnotationEnd(annotation_element)
299            self._insert(
300                annotation_end, after=content, position=position, main_text=True
301            )
302            return annotation_element
303
304        # With "(int, int)" =>  automatically insert a "start" and an "end"
305        # bookmark
306        if (
307            before is None
308            and after is None
309            and content is None
310            and isinstance(position, tuple)
311        ):
312            # Start
313            self._insert(annotation_element, position=position[0], main_text=True)
314            # End
315            annotation_end = AnnotationEnd(annotation_element)
316            self._insert(annotation_end, position=position[1], main_text=True)
317            return annotation_element
318
319        # Without "content" nor "position"
320        if content is not None or not isinstance(position, int):
321            raise ValueError("Bad arguments")
322
323        # Insert
324        self._insert(
325            annotation_element,
326            before=before,
327            after=after,
328            position=position,
329            main_text=True,
330        )
331        return annotation_element

Insert an annotation, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full content regex. Else, the annotation is positionned either 'before' or 'after' provided regex.

If content is an odf element (ie: paragraph, span, ...), the full inner content is covered by the annotation (of the position just after if content is a single empty tag).

If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.

annotation_element can contain a previously created annotation, else the annotation is created from the body, creator and optional date (current date by default).

Arguments:

annotation_element -- Annotation or None

before -- str regular expression or None

after -- str regular expression or Element or None

content -- str regular expression or None, or Element

position -- int or tuple of int

body -- str or Element

creator -- str

date -- datetime
def insert_annotation_end( self, annotation_element: Annotation, before: str | None = None, after: str | None = None, position: int = 0) -> AnnotationEnd:
333    def insert_annotation_end(
334        self,
335        annotation_element: Annotation,
336        before: str | None = None,
337        after: str | None = None,
338        position: int = 0,
339    ) -> AnnotationEnd:
340        """Insert an annotation end tag for an existing annotation. If some end
341        tag already exists, replace it. Annotation end tag is set at the
342        position defined by the regex (before or after).
343
344        If content/before or after (regex) returns a group of matching
345        positions, the position value is the index of matching place to use.
346
347        Arguments:
348
349            annotation_element -- Annotation (mandatory)
350
351            before -- str regular expression or None
352
353            after -- str regular expression or None
354
355            position -- int
356        """
357
358        if annotation_element is None:
359            raise ValueError
360        if not isinstance(annotation_element, Annotation):
361            raise TypeError("Not a <office:annotation> Annotation")
362
363        # remove existing end tag
364        name = annotation_element.name
365        existing_end_tag = self.get_annotation_end(name=name)
366        if existing_end_tag:
367            existing_end_tag.delete()
368
369        # create the end tag
370        end_tag = AnnotationEnd(annotation_element)
371
372        # Insert
373        self._insert(
374            end_tag, before=before, after=after, position=position, main_text=True
375        )
376        return end_tag

Insert an annotation end tag for an existing annotation. If some end tag already exists, replace it. Annotation end tag is set at the position defined by the regex (before or after).

If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.

Arguments:

annotation_element -- Annotation (mandatory)

before -- str regular expression or None

after -- str regular expression or None

position -- int
def set_reference_mark( self, name: str, before: str | None = None, after: str | None = None, position: int = 0, content: str | Element | None = None) -> Element:
378    def set_reference_mark(
379        self,
380        name: str,
381        before: str | None = None,
382        after: str | None = None,
383        position: int = 0,
384        content: str | Element | None = None,
385    ) -> Element:
386        """Insert a reference mark, at the position defined by the regex
387        (before, after, content) or by positionnal argument (position). If
388        content is provided, the annotation covers the full range content regex
389        (instances of ReferenceMarkStart and ReferenceMarkEnd are
390        created). Else, an instance of ReferenceMark is positionned either
391        'before' or 'after' provided regex.
392
393        If content is an ODF Element (ie: Paragraph, Span, ...), the full inner
394        content is referenced (of the position just after if content is a single
395        empty tag).
396
397        If content/before or after exists (regex) and return a group of matching
398        positions, the position value is the index of matching place to use.
399
400        Name is mandatory and shall be unique in the document for the preference
401        mark range.
402
403        Arguments:
404
405            name -- str
406
407            before -- str regular expression or None
408
409            after -- str regular expression or None,
410
411            content -- str regular expression or None, or Element
412
413            position -- int or tuple of int
414
415        Return: the created ReferenceMark or ReferenceMarkStart
416        """
417        # special case: content is an odf element (ie: a paragraph)
418        if isinstance(content, Element):
419            if content.is_empty():
420                reference = ReferenceMark(name)
421                content.insert(reference, xmlposition=NEXT_SIBLING)
422                return reference
423            reference_start = ReferenceMarkStart(name)
424            content.insert(reference_start, start=True)
425            reference_end = ReferenceMarkEnd(name)
426            content.append(reference_end)
427            return reference_start
428
429        # With "content" => automatically insert a "start" and an "end"
430        # reference
431        if (
432            before is None
433            and after is None
434            and content is not None
435            and isinstance(position, int)
436        ):
437            # Start tag
438            reference_start = ReferenceMarkStart(name)
439            self._insert(
440                reference_start, before=content, position=position, main_text=True
441            )
442            # End tag
443            reference_end = ReferenceMarkEnd(name)
444            self._insert(
445                reference_end, after=content, position=position, main_text=True
446            )
447            return reference_start
448
449        # With "(int, int)" =>  automatically insert a "start" and an "end"
450        if (
451            before is None
452            and after is None
453            and content is None
454            and isinstance(position, tuple)
455        ):
456            # Start
457            reference_start = ReferenceMarkStart(name)
458            self._insert(reference_start, position=position[0], main_text=True)
459            # End
460            reference_end = ReferenceMarkEnd(name)
461            self._insert(reference_end, position=position[1], main_text=True)
462            return reference_start
463
464        # Without "content" nor "position"
465        if content is not None or not isinstance(position, int):
466            raise ValueError("bad arguments")
467
468        # Insert a positional reference mark
469        reference = ReferenceMark(name)
470        self._insert(
471            reference,
472            before=before,
473            after=after,
474            position=position,
475            main_text=True,
476        )
477        return reference

Insert a reference mark, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full range content regex (instances of ReferenceMarkStart and ReferenceMarkEnd are created). Else, an instance of ReferenceMark is positionned either 'before' or 'after' provided regex.

If content is an ODF Element (ie: Paragraph, Span, ...), the full inner content is referenced (of the position just after if content is a single empty tag).

If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.

Name is mandatory and shall be unique in the document for the preference mark range.

Arguments:

name -- str

before -- str regular expression or None

after -- str regular expression or None,

content -- str regular expression or None, or Element

position -- int or tuple of int

Return: the created ReferenceMark or ReferenceMarkStart

def set_reference_mark_end( self, reference_mark: Element, before: str | None = None, after: str | None = None, position: int = 0) -> ReferenceMarkEnd:
479    def set_reference_mark_end(
480        self,
481        reference_mark: Element,
482        before: str | None = None,
483        after: str | None = None,
484        position: int = 0,
485    ) -> ReferenceMarkEnd:
486        """Insert/move a ReferenceMarkEnd for an existing reference mark. If
487        some end tag already exists, replace it. Reference tag is set at the
488        position defined by the regex (before or after).
489
490        If content/before or after (regex) returns a group of matching
491        positions, the position value is the index of matching place to use.
492
493        Arguments:
494
495            reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
496
497            before -- str regular expression or None
498
499            after -- str regular expression or None
500
501            position -- int
502        """
503        if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)):
504            raise TypeError("Not a ReferenceMark or ReferenceMarkStart")
505        name = reference_mark.name
506        if isinstance(reference_mark, ReferenceMark):
507            # change it to a range reference:
508            reference_mark.tag = ReferenceMarkStart._tag
509
510        existing_end_tag = self.get_reference_mark_end(name=name)
511        if existing_end_tag:
512            existing_end_tag.delete()
513
514        # create the end tag
515        end_tag = ReferenceMarkEnd(name)
516
517        # Insert
518        self._insert(
519            end_tag, before=before, after=after, position=position, main_text=True
520        )
521        return end_tag

Insert/move a ReferenceMarkEnd for an existing reference mark. If some end tag already exists, replace it. Reference tag is set at the position defined by the regex (before or after).

If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.

Arguments:

reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)

before -- str regular expression or None

after -- str regular expression or None

position -- int
def insert_variable(self, variable_element: Element, after: str | None) -> None:
523    def insert_variable(self, variable_element: Element, after: str | None) -> None:
524        self._insert(variable_element, after=after, main_text=True)
def set_span( self, match: str, tail: str, style: str, regex: str | None = None, offset: int | None = None, length: int = 0) -> Span:
526    @_by_regex_offset
527    def set_span(
528        self,
529        match: str,
530        tail: str,
531        style: str,
532        regex: str | None = None,
533        offset: int | None = None,
534        length: int = 0,
535    ) -> Span:
536        """
537        set_span(style, regex=None, offset=None, length=0)
538        Apply the given style to text content matching the regex OR the
539        positional arguments offset and length.
540
541        (match, tail: provided by regex decorator)
542
543        Arguments:
544
545            style -- str
546
547            regex -- str regular expression
548
549            offset -- int
550
551            length -- int
552        """
553        span = Span(match, style=style)
554        span.tail = tail
555        return span

set_span(style, regex=None, offset=None, length=0) Apply the given style to text content matching the regex OR the positional arguments offset and length.

(match, tail: provided by regex decorator)

Arguments:

style -- str

regex -- str regular expression

offset -- int

length -- int
def remove_spans(self, keep_heading: bool = True) -> Element | list:
557    def remove_spans(self, keep_heading: bool = True) -> Element | list:
558        """Send back a copy of the element, without span styles.
559        If keep_heading is True (default), the first level heading style is left
560        unchanged.
561        """
562        strip = (Span._tag,)
563        if keep_heading:
564            protect = ("text:h",)
565        else:
566            protect = None
567        return self.strip_tags(strip=strip, protect=protect)

Send back a copy of the element, without span styles. If keep_heading is True (default), the first level heading style is left unchanged.

def remove_span( self, spans: Element | list[Element]) -> Element | list:
569    def remove_span(self, spans: Element | list[Element]) -> Element | list:
570        """Send back a copy of the element, the spans (not a clone) removed.
571
572        Arguments:
573
574            spans -- Element or list of Element
575        """
576        return self.strip_elements(spans)

Send back a copy of the element, the spans (not a clone) removed.

Arguments:

spans -- Element or list of Element
def insert_reference( self, name: str, ref_format: str = '', before: str | None = None, after: str | Element | None = None, position: int = 0, display: str | None = None) -> None:
624    def insert_reference(
625        self,
626        name: str,
627        ref_format: str = "",
628        before: str | None = None,
629        after: str | Element | None = None,
630        position: int = 0,
631        display: str | None = None,
632    ) -> None:
633        """Create and insert a reference to a content marked by a reference
634        mark. The Reference element ("text:reference-ref") represents a
635        field that references a "text:reference-mark-start" or
636        "text:reference-mark" element. Its "text:reference-format" attribute
637        specifies what is displayed from the referenced element. Default is
638        'page'. Actual content is not automatically updated except for the 'text'
639        format.
640
641        name is mandatory and should represent an existing reference mark of the
642        document.
643
644        ref_format is the argument for format reference (default is 'page').
645
646        The reference is inserted the position defined by the regex (before /
647        after), or by positionnal argument (position). If 'display' is provided,
648        it will be used as the text value for the reference.
649
650        If after is an ODF Element, the reference is inserted as first child of
651        this element.
652
653        Arguments:
654
655            name -- str
656
657            ref_format -- one of : 'chapter', 'direction', 'page', 'text',
658                                    'caption', 'category-and-value', 'value',
659                                    'number', 'number-all-superior',
660                                    'number-no-superior'
661
662            before -- str regular expression or None
663
664            after -- str regular expression or odf element or None
665
666            position -- int
667
668            display -- str or None
669        """
670        reference = Reference(name, ref_format)
671        if display is None and ref_format == "text":
672            # get reference content
673            body = self.document_body
674            if not body:
675                body = self.root
676            mark = body.get_reference_mark(name=name)
677            if mark:
678                display = mark.referenced_text  # type: ignore
679        if not display:
680            display = " "
681        reference.text = display
682        if isinstance(after, Element):
683            after.insert(reference, FIRST_CHILD)
684        else:
685            self._insert(
686                reference, before=before, after=after, position=position, main_text=True
687            )

Create and insert a reference to a content marked by a reference mark. The Reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its "text:reference-format" attribute specifies what is displayed from the referenced element. Default is 'page'. Actual content is not automatically updated except for the 'text' format.

name is mandatory and should represent an existing reference mark of the document.

ref_format is the argument for format reference (default is 'page').

The reference is inserted the position defined by the regex (before / after), or by positionnal argument (position). If 'display' is provided, it will be used as the text value for the reference.

If after is an ODF Element, the reference is inserted as first child of this element.

Arguments:

name -- str

ref_format -- one of : 'chapter', 'direction', 'page', 'text',
                        'caption', 'category-and-value', 'value',
                        'number', 'number-all-superior',
                        'number-no-superior'

before -- str regular expression or None

after -- str regular expression or odf element or None

position -- int

display -- str or None
def set_bookmark( self, name: str, before: str | None = None, after: str | None = None, position: int | tuple = 0, role: str | None = None, content: str | None = None) -> Element | tuple[Element, Element]:
689    def set_bookmark(
690        self,
691        name: str,
692        before: str | None = None,
693        after: str | None = None,
694        position: int | tuple = 0,
695        role: str | None = None,
696        content: str | None = None,
697    ) -> Element | tuple[Element, Element]:
698        """Insert a bookmark before or after the characters in the text which
699        match the regex before/after. When the regex matches more of one part
700        of the text, position can be set to choose which part must be used.
701        If before and after are None, we use only position that is the number
702        of characters.
703
704        So, by default, this function inserts a bookmark before the first
705        character of the content. Role can be None, "start" or "end", we
706        insert respectively a position bookmark a bookmark-start or a
707        bookmark-end.
708
709        If content is not None these 2 calls are equivalent:
710
711          paragraph.set_bookmark("bookmark", content="xyz")
712
713        and:
714
715          paragraph.set_bookmark("bookmark", before="xyz", role="start")
716          paragraph.set_bookmark("bookmark", after="xyz", role="end")
717
718
719        If position is a 2-tuple, these 2 calls are equivalent:
720
721          paragraph.set_bookmark("bookmark", position=(10, 20))
722
723        and:
724
725          paragraph.set_bookmark("bookmark", position=10, role="start")
726          paragraph.set_bookmark("bookmark", position=20, role="end")
727
728
729        Arguments:
730
731            name -- str
732
733            before -- str regex
734
735            after -- str regex
736
737            position -- int or (int, int)
738
739            role -- None, "start" or "end"
740
741            content -- str regex
742        """
743        # With "content" => automatically insert a "start" and an "end"
744        # bookmark
745        if (
746            before is None
747            and after is None
748            and role is None
749            and content is not None
750            and isinstance(position, int)
751        ):
752            # Start
753            start = BookmarkStart(name)
754            self._insert(start, before=content, position=position, main_text=True)
755            # End
756            end = BookmarkEnd(name)
757            self._insert(end, after=content, position=position, main_text=True)
758            return start, end
759
760        # With "(int, int)" =>  automatically insert a "start" and an "end"
761        # bookmark
762        if (
763            before is None
764            and after is None
765            and role is None
766            and content is None
767            and isinstance(position, tuple)
768        ):
769            # Start
770            start = BookmarkStart(name)
771            self._insert(start, position=position[0], main_text=True)
772            # End
773            end = BookmarkEnd(name)
774            self._insert(end, position=position[1], main_text=True)
775            return start, end
776
777        # Without "content" nor "position"
778        if content is not None or not isinstance(position, int):
779            raise ValueError("bad arguments")
780
781        # Role
782        if role is None:
783            bookmark: Element = Bookmark(name)
784        elif role == "start":
785            bookmark = BookmarkStart(name)
786        elif role == "end":
787            bookmark = BookmarkEnd(name)
788        else:
789            raise ValueError("bad arguments")
790
791        # Insert
792        self._insert(
793            bookmark, before=before, after=after, position=position, main_text=True
794        )
795
796        return bookmark

Insert a bookmark before or after the characters in the text which match the regex before/after. When the regex matches more of one part of the text, position can be set to choose which part must be used. If before and after are None, we use only position that is the number of characters.

So, by default, this function inserts a bookmark before the first character of the content. Role can be None, "start" or "end", we insert respectively a position bookmark a bookmark-start or a bookmark-end.

If content is not None these 2 calls are equivalent:

paragraph.set_bookmark("bookmark", content="xyz")

and:

paragraph.set_bookmark("bookmark", before="xyz", role="start") paragraph.set_bookmark("bookmark", after="xyz", role="end")

If position is a 2-tuple, these 2 calls are equivalent:

paragraph.set_bookmark("bookmark", position=(10, 20))

and:

paragraph.set_bookmark("bookmark", position=10, role="start") paragraph.set_bookmark("bookmark", position=20, role="end")

Arguments:

name -- str

before -- str regex

after -- str regex

position -- int or (int, int)

role -- None, "start" or "end"

content -- str regex
Inherited Members
odfdo.paragraph_base.ParagraphBase
get_formatted_text
append_plain_text
style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
def PageBreak() -> Paragraph:
831def PageBreak() -> Paragraph:
832    """Return an empty paragraph with a manual page break.
833
834    Using this function requires to register the page break style with:
835        document.add_page_break_style()
836    """
837    return Paragraph("", style="odfdopagebreak")

Return an empty paragraph with a manual page break.

Using this function requires to register the page break style with: document.add_page_break_style()

class RectangleShape(odfdo.shapes.ShapeBase):
146class RectangleShape(ShapeBase):
147    """Create a rectangle shape.
148
149    Arguments:
150
151        style -- str
152
153        text_style -- str
154
155        draw_id -- str
156
157        layer -- str
158
159        position -- (str, str)
160
161        size -- (str, str)
162
163    """
164
165    _tag = "draw:rect"
166    _properties: tuple[PropDef, ...] = ()
167
168    def __init__(
169        self,
170        style: str | None = None,
171        text_style: str | None = None,
172        draw_id: str | None = None,
173        layer: str | None = None,
174        position: tuple | None = None,
175        size: tuple | None = None,
176        **kwargs: Any,
177    ) -> None:
178        kwargs.update(
179            {
180                "style": style,
181                "text_style": text_style,
182                "draw_id": draw_id,
183                "layer": layer,
184                "size": size,
185                "position": position,
186            }
187        )
188        super().__init__(**kwargs)

Create a rectangle shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

position -- (str, str)

size -- (str, str)
RectangleShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, position: tuple | None = None, size: tuple | None = None, **kwargs: Any)
168    def __init__(
169        self,
170        style: str | None = None,
171        text_style: str | None = None,
172        draw_id: str | None = None,
173        layer: str | None = None,
174        position: tuple | None = None,
175        size: tuple | None = None,
176        **kwargs: Any,
177    ) -> None:
178        kwargs.update(
179            {
180                "style": style,
181                "text_style": text_style,
182                "draw_id": draw_id,
183                "layer": layer,
184                "size": size,
185                "position": position,
186            }
187        )
188        super().__init__(**kwargs)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class Reference(odfdo.Element):
 59class Reference(Element):
 60    """A reference to a content marked by a reference mark.
 61    The odf_reference element ("text:reference-ref") represents a field that
 62    references a "text:reference-mark-start" or "text:reference-mark" element.
 63    Its text:reference-format attribute specifies what is displayed from the
 64    referenced element. Default is 'page'
 65    Actual content is not updated except for the 'text' format by the
 66    update() method.
 67
 68
 69    Creation of references can be tricky, consider using this method:
 70        odfdo.paragraph.insert_reference()
 71
 72    Values for text:reference-format :
 73        The defined values for the text:reference-format attribute supported by
 74        all reference fields are:
 75          - 'chapter': displays the number of the chapter in which the
 76            referenced item appears.
 77          - 'direction': displays whether the referenced item is above or
 78            below the reference field.
 79          - 'page': displays the number of the page on which the referenced
 80            item appears.
 81          - 'text': displays the text of the referenced item.
 82        Additional defined values for the text:reference-format attribute
 83        supported by references to sequence fields are:
 84          - 'caption': displays the caption in which the sequence is used.
 85          - 'category-and-value': displays the name and value of the sequence.
 86          - 'value': displays the value of the sequence.
 87
 88        References to bookmarks and other references support additional values,
 89        which display the list label of the referenced item. If the referenced
 90        item is contained in a list or a numbered paragraph, the list label is
 91        the formatted number of the paragraph which contains the referenced
 92        item. If the referenced item is not contained in a list or numbered
 93        paragraph, the list label is empty, and the referenced field therefore
 94        displays nothing. If the referenced bookmark or reference contains more
 95        than one paragraph, the list label of the paragraph at which the
 96        bookmark or reference starts is taken.
 97
 98        Additional defined values for the text:reference-format attribute
 99        supported by all references to bookmark's or other reference fields
100        are:
101          - 'number': displays the list label of the referenced item. [...]
102          - 'number-all-superior': displays the list label of the referenced
103            item and adds the contents of all list labels of superior levels
104            in front of it. [...]
105          - 'number-no-superior': displays the contents of the list label of
106            the referenced item.
107    """
108
109    _tag = "text:reference-ref"
110    _properties = (PropDef("name", "text:ref-name"),)
111    format_allowed = (
112        "chapter",
113        "direction",
114        "page",
115        "text",
116        "caption",
117        "category-and-value",
118        "value",
119        "number",
120        "number-all-superior",
121        "number-no-superior",
122    )
123
124    def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None:
125        """Create a reference to a content marked by a reference mark. An
126        actual reference mark with the provided name should exist.
127
128        Consider using: odfdo.paragraph.insert_reference()
129
130        The text:ref-name attribute identifies a "text:reference-mark" or
131        "text:referencemark-start" element by the value of that element's
132        text:name attribute.
133        If ref_format is 'text', the current text content of the reference_mark
134        is retrieved.
135
136        Arguments:
137
138            name -- str : name of the reference mark
139
140            ref_format -- str : format of the field. Default is 'page', allowed
141                            values are 'chapter', 'direction', 'page', 'text',
142                            'caption', 'category-and-value', 'value', 'number',
143                            'number-all-superior', 'number-no-superior'.
144        """
145        super().__init__(**kwargs)
146        if self._do_init:
147            self.name = name
148            self.ref_format = ref_format
149
150    @property
151    def ref_format(self) -> str | None:
152        reference = self.get_attribute("text:reference-format")
153        if isinstance(reference, str):
154            return reference
155        return None
156
157    @ref_format.setter
158    def ref_format(self, ref_format: str) -> None:
159        """Set the text:reference-format attribute.
160
161        Arguments:
162
163            ref_format -- str
164        """
165        if not ref_format or ref_format not in self.format_allowed:
166            ref_format = "page"
167        self.set_attribute("text:reference-format", ref_format)
168
169    def update(self) -> None:
170        """Update the content of the reference text field. Currently only
171        'text' format is implemented. Other values, for example the 'page' text
172        field, may need to be refreshed through a visual ODF parser.
173        """
174        ref_format = self.ref_format
175        if ref_format != "text":
176            # only 'text' is implemented
177            return None
178        body = self.document_body
179        if not body:
180            body = self.root
181        name = self.name
182        reference = body.get_reference_mark(name=name)
183        if not reference:
184            return None
185        # we know it is a ReferenceMarkStart:
186        self.text = reference.referenced_text()  # type: ignore

A reference to a content marked by a reference mark. The odf_reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its text:reference-format attribute specifies what is displayed from the referenced element. Default is 'page' Actual content is not updated except for the 'text' format by the update() method.

Creation of references can be tricky, consider using this method: odfdo.paragraph.insert_reference()

Values for text:reference-format : The defined values for the text:reference-format attribute supported by all reference fields are: - 'chapter': displays the number of the chapter in which the referenced item appears. - 'direction': displays whether the referenced item is above or below the reference field. - 'page': displays the number of the page on which the referenced item appears. - 'text': displays the text of the referenced item. Additional defined values for the text:reference-format attribute supported by references to sequence fields are: - 'caption': displays the caption in which the sequence is used. - 'category-and-value': displays the name and value of the sequence. - 'value': displays the value of the sequence.

References to bookmarks and other references support additional values,
which display the list label of the referenced item. If the referenced
item is contained in a list or a numbered paragraph, the list label is
the formatted number of the paragraph which contains the referenced
item. If the referenced item is not contained in a list or numbered
paragraph, the list label is empty, and the referenced field therefore
displays nothing. If the referenced bookmark or reference contains more
than one paragraph, the list label of the paragraph at which the
bookmark or reference starts is taken.

Additional defined values for the text:reference-format attribute
supported by all references to bookmark's or other reference fields
are:
  - 'number': displays the list label of the referenced item. [...]
  - 'number-all-superior': displays the list label of the referenced
    item and adds the contents of all list labels of superior levels
    in front of it. [...]
  - 'number-no-superior': displays the contents of the list label of
    the referenced item.
Reference(name: str = '', ref_format: str = '', **kwargs: Any)
124    def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None:
125        """Create a reference to a content marked by a reference mark. An
126        actual reference mark with the provided name should exist.
127
128        Consider using: odfdo.paragraph.insert_reference()
129
130        The text:ref-name attribute identifies a "text:reference-mark" or
131        "text:referencemark-start" element by the value of that element's
132        text:name attribute.
133        If ref_format is 'text', the current text content of the reference_mark
134        is retrieved.
135
136        Arguments:
137
138            name -- str : name of the reference mark
139
140            ref_format -- str : format of the field. Default is 'page', allowed
141                            values are 'chapter', 'direction', 'page', 'text',
142                            'caption', 'category-and-value', 'value', 'number',
143                            'number-all-superior', 'number-no-superior'.
144        """
145        super().__init__(**kwargs)
146        if self._do_init:
147            self.name = name
148            self.ref_format = ref_format

Create a reference to a content marked by a reference mark. An actual reference mark with the provided name should exist.

Consider using: odfdo.paragraph.insert_reference()

The text:ref-name attribute identifies a "text:reference-mark" or "text:referencemark-start" element by the value of that element's text:name attribute. If ref_format is 'text', the current text content of the reference_mark is retrieved.

Arguments:

name -- str : name of the reference mark

ref_format -- str : format of the field. Default is 'page', allowed
                values are 'chapter', 'direction', 'page', 'text',
                'caption', 'category-and-value', 'value', 'number',
                'number-all-superior', 'number-no-superior'.
format_allowed = ('chapter', 'direction', 'page', 'text', 'caption', 'category-and-value', 'value', 'number', 'number-all-superior', 'number-no-superior')
ref_format: str | None
150    @property
151    def ref_format(self) -> str | None:
152        reference = self.get_attribute("text:reference-format")
153        if isinstance(reference, str):
154            return reference
155        return None

Set the text:reference-format attribute.

Arguments:

ref_format -- str
def update(self) -> None:
169    def update(self) -> None:
170        """Update the content of the reference text field. Currently only
171        'text' format is implemented. Other values, for example the 'page' text
172        field, may need to be refreshed through a visual ODF parser.
173        """
174        ref_format = self.ref_format
175        if ref_format != "text":
176            # only 'text' is implemented
177            return None
178        body = self.document_body
179        if not body:
180            body = self.root
181        name = self.name
182        reference = body.get_reference_mark(name=name)
183        if not reference:
184            return None
185        # we know it is a ReferenceMarkStart:
186        self.text = reference.referenced_text()  # type: ignore

Update the content of the reference text field. Currently only 'text' format is implemented. Other values, for example the 'page' text field, may need to be refreshed through a visual ODF parser.

name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMark(odfdo.Element):
192class ReferenceMark(Element):
193    """A point reference.
194    A point reference marks a position in text and is represented by a single
195    "text:reference-mark" element.
196    """
197
198    _tag = "text:reference-mark"
199    _properties = (PropDef("name", "text:name"),)
200
201    def __init__(self, name: str = "", **kwargs: Any) -> None:
202        """A point reference. A point reference marks a position in text and is
203        represented by a single "text:reference-mark" element.
204        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
205
206        Arguments:
207
208            name -- str
209        """
210        super().__init__(**kwargs)
211        if self._do_init:
212            self.name = name

A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element.

ReferenceMark(name: str = '', **kwargs: Any)
201    def __init__(self, name: str = "", **kwargs: Any) -> None:
202        """A point reference. A point reference marks a position in text and is
203        represented by a single "text:reference-mark" element.
204        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
205
206        Arguments:
207
208            name -- str
209        """
210        super().__init__(**kwargs)
211        if self._do_init:
212            self.name = name

A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element. Consider using the wrapper: odfdo.paragraph.set_reference_mark()

Arguments:

name -- str
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMarkEnd(odfdo.Element):
218class ReferenceMarkEnd(Element):
219    """The "text:reference-mark-end" element represents the end of a range
220    reference.
221    """
222
223    _tag = "text:reference-mark-end"
224    _properties = (PropDef("name", "text:name"),)
225
226    def __init__(self, name: str = "", **kwargs: Any) -> None:
227        """The "text:reference-mark-end" element represent the end of a range
228        reference.
229        Consider using the wrappers: odfdo.paragraph.set_reference_mark() and
230        odfdo.paragraph.set_reference_mark_end()
231
232        Arguments:
233
234            name -- str
235        """
236        super().__init__(**kwargs)
237        if self._do_init:
238            self.name = name
239
240    def referenced_text(self) -> str:
241        """Return the text between reference-mark-start and reference-mark-end."""
242        name = self.name
243        request = (
244            f"//text()"
245            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
246            f"and following::text:reference-mark-end[@text:name='{name}']]"
247        )
248        result = " ".join(str(x) for x in self.xpath(request))
249        return result

The "text:reference-mark-end" element represents the end of a range reference.

ReferenceMarkEnd(name: str = '', **kwargs: Any)
226    def __init__(self, name: str = "", **kwargs: Any) -> None:
227        """The "text:reference-mark-end" element represent the end of a range
228        reference.
229        Consider using the wrappers: odfdo.paragraph.set_reference_mark() and
230        odfdo.paragraph.set_reference_mark_end()
231
232        Arguments:
233
234            name -- str
235        """
236        super().__init__(**kwargs)
237        if self._do_init:
238            self.name = name

The "text:reference-mark-end" element represent the end of a range reference. Consider using the wrappers: odfdo.paragraph.set_reference_mark() and odfdo.paragraph.set_reference_mark_end()

Arguments:

name -- str
def referenced_text(self) -> str:
240    def referenced_text(self) -> str:
241        """Return the text between reference-mark-start and reference-mark-end."""
242        name = self.name
243        request = (
244            f"//text()"
245            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
246            f"and following::text:reference-mark-end[@text:name='{name}']]"
247        )
248        result = " ".join(str(x) for x in self.xpath(request))
249        return result

Return the text between reference-mark-start and reference-mark-end.

name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMarkStart(odfdo.Element):
255class ReferenceMarkStart(Element):
256    """The "text:reference-mark-start" element represents the start of a
257    range reference.
258    """
259
260    _tag = "text:reference-mark-start"
261    _properties = (PropDef("name", "text:name"),)
262
263    def __init__(self, name: str = "", **kwargs: Any) -> None:
264        """The "text:reference-mark-start" element represent the start of a range
265        reference.
266        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
267
268        Arguments:
269
270            name -- str
271        """
272        super().__init__(**kwargs)
273        if self._do_init:
274            self.name = name
275
276    def referenced_text(self) -> str:
277        """Return the text between reference-mark-start and reference-mark-end."""
278        name = self.name
279        request = (
280            f"//text()"
281            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
282            f"and following::text:reference-mark-end[@text:name='{name}']]"
283        )
284        result = " ".join(str(x) for x in self.xpath(request))
285        return result
286
287    def get_referenced(
288        self,
289        no_header: bool = False,
290        clean: bool = True,
291        as_xml: bool = False,
292        as_list: bool = False,
293    ) -> Element | list | str | None:
294        """Return the document content between the start and end tags of the
295        reference. The content returned by this method can spread over several
296        headers and paragraphs.
297        By default, the content is returned as an "office:text" odf element.
298
299
300        Arguments:
301
302            no_header -- boolean (default to False), translate existing headers
303                         tags "text:h" into paragraphs "text:p".
304
305            clean -- boolean (default to True), suppress unwanted tags. Striped
306                     tags are : 'text:change', 'text:change-start',
307                     'text:change-end', 'text:reference-mark',
308                     'text:reference-mark-start', 'text:reference-mark-end'.
309
310            as_xml -- boolean (default to False), format the returned content as
311                      a XML string (serialization).
312
313            as_list -- boolean (default to False), do not embed the returned
314                       content in a "office:text'" element, instead simply
315                       return a raw list of odf elements.
316        """
317        name = self.name
318        parent = self.parent
319        if parent is None:
320            raise ValueError("Reference need some upper document part")
321        body = self.document_body
322        if not body:
323            body = parent
324        end = body.get_reference_mark_end(name=name)
325        if end is None:
326            raise ValueError("No reference-end found")
327        start = self
328        return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)
329
330    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
331        """Delete the given element from the XML tree. If no element is given,
332        "self" is deleted. The XML library may allow to continue to use an
333        element now "orphan" as long as you have a reference to it.
334
335        For odf_reference_mark_start : delete the reference-end tag if exists.
336
337        Arguments:
338
339            child -- Element
340
341            keep_tail -- boolean (default to True), True for most usages.
342        """
343        if child is not None:  # act like normal delete
344            return super().delete(child, keep_tail)
345        name = self.name
346        parent = self.parent
347        if parent is None:
348            raise ValueError("Can't delete the root element")
349        body = self.document_body
350        if not body:
351            body = parent
352        end = body.get_reference_mark_end(name=name)
353        if end:
354            end.delete()
355        # act like normal delete
356        return super().delete()

The "text:reference-mark-start" element represents the start of a range reference.

ReferenceMarkStart(name: str = '', **kwargs: Any)
263    def __init__(self, name: str = "", **kwargs: Any) -> None:
264        """The "text:reference-mark-start" element represent the start of a range
265        reference.
266        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
267
268        Arguments:
269
270            name -- str
271        """
272        super().__init__(**kwargs)
273        if self._do_init:
274            self.name = name

The "text:reference-mark-start" element represent the start of a range reference. Consider using the wrapper: odfdo.paragraph.set_reference_mark()

Arguments:

name -- str
def referenced_text(self) -> str:
276    def referenced_text(self) -> str:
277        """Return the text between reference-mark-start and reference-mark-end."""
278        name = self.name
279        request = (
280            f"//text()"
281            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
282            f"and following::text:reference-mark-end[@text:name='{name}']]"
283        )
284        result = " ".join(str(x) for x in self.xpath(request))
285        return result

Return the text between reference-mark-start and reference-mark-end.

def get_referenced( self, no_header: bool = False, clean: bool = True, as_xml: bool = False, as_list: bool = False) -> Element | list | str | None:
287    def get_referenced(
288        self,
289        no_header: bool = False,
290        clean: bool = True,
291        as_xml: bool = False,
292        as_list: bool = False,
293    ) -> Element | list | str | None:
294        """Return the document content between the start and end tags of the
295        reference. The content returned by this method can spread over several
296        headers and paragraphs.
297        By default, the content is returned as an "office:text" odf element.
298
299
300        Arguments:
301
302            no_header -- boolean (default to False), translate existing headers
303                         tags "text:h" into paragraphs "text:p".
304
305            clean -- boolean (default to True), suppress unwanted tags. Striped
306                     tags are : 'text:change', 'text:change-start',
307                     'text:change-end', 'text:reference-mark',
308                     'text:reference-mark-start', 'text:reference-mark-end'.
309
310            as_xml -- boolean (default to False), format the returned content as
311                      a XML string (serialization).
312
313            as_list -- boolean (default to False), do not embed the returned
314                       content in a "office:text'" element, instead simply
315                       return a raw list of odf elements.
316        """
317        name = self.name
318        parent = self.parent
319        if parent is None:
320            raise ValueError("Reference need some upper document part")
321        body = self.document_body
322        if not body:
323            body = parent
324        end = body.get_reference_mark_end(name=name)
325        if end is None:
326            raise ValueError("No reference-end found")
327        start = self
328        return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)

Return the document content between the start and end tags of the reference. The content returned by this method can spread over several headers and paragraphs. By default, the content is returned as an "office:text" odf element.

Arguments:

no_header -- boolean (default to False), translate existing headers
             tags "text:h" into paragraphs "text:p".

clean -- boolean (default to True), suppress unwanted tags. Striped
         tags are : 'text:change', 'text:change-start',
         'text:change-end', 'text:reference-mark',
         'text:reference-mark-start', 'text:reference-mark-end'.

as_xml -- boolean (default to False), format the returned content as
          a XML string (serialization).

as_list -- boolean (default to False), do not embed the returned
           content in a "office:text'" element, instead simply
           return a raw list of odf elements.
def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
330    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
331        """Delete the given element from the XML tree. If no element is given,
332        "self" is deleted. The XML library may allow to continue to use an
333        element now "orphan" as long as you have a reference to it.
334
335        For odf_reference_mark_start : delete the reference-end tag if exists.
336
337        Arguments:
338
339            child -- Element
340
341            keep_tail -- boolean (default to True), True for most usages.
342        """
343        if child is not None:  # act like normal delete
344            return super().delete(child, keep_tail)
345        name = self.name
346        parent = self.parent
347        if parent is None:
348            raise ValueError("Can't delete the root element")
349        body = self.document_body
350        if not body:
351            body = parent
352        end = body.get_reference_mark_end(name=name)
353        if end:
354            end.delete()
355        # act like normal delete
356        return super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For odf_reference_mark_start : delete the reference-end tag if exists.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Row(odfdo.Element):
 52class Row(Element):
 53    """ODF table row "table:table-row" """
 54
 55    _tag = "table:table-row"
 56    _caching = True
 57    _append = Element.append
 58
 59    def __init__(
 60        self,
 61        width: int | None = None,
 62        repeated: int | None = None,
 63        style: str | None = None,
 64        **kwargs: Any,
 65    ) -> None:
 66        """create a Row, optionally filled with "width" number of cells.
 67
 68        Rows contain cells, their number determine the number of columns.
 69
 70        You don't generally have to create rows by hand, use the Table API.
 71
 72        Arguments:
 73
 74            width -- int
 75
 76            repeated -- int
 77
 78            style -- str
 79        """
 80        super().__init__(**kwargs)
 81        self.y = None
 82        if not hasattr(self, "_indexes"):
 83            self._indexes = {}
 84            self._indexes["_rmap"] = {}
 85        if not hasattr(self, "_rmap"):
 86            self._compute_row_cache()
 87            if not hasattr(self, "_tmap"):
 88                self._tmap = []
 89                self._cmap = []
 90        if self._do_init:
 91            if width is not None:
 92                for _i in range(width):
 93                    self.append(Cell())  # type:ignore
 94            if repeated:
 95                self.repeated = repeated
 96            if style is not None:
 97                self.style = style
 98            self._compute_row_cache()
 99
100    def _get_cells(self) -> list[Element]:
101        return self.get_elements(_xpath_cell)
102
103    def _translate_row_coordinates(
104        self,
105        coord: tuple | list | str,
106    ) -> tuple[int | None, int | None]:
107        xyzt = convert_coordinates(coord)
108        if len(xyzt) == 2:
109            x, z = xyzt
110        else:
111            x, _, z, __ = xyzt
112        if x and x < 0:
113            x = increment(x, self.width)
114        if z and z < 0:
115            z = increment(z, self.width)
116        return (x, z)
117
118    def _compute_row_cache(self) -> None:
119        idx_repeated_seq = self.elements_repeated_sequence(
120            _xpath_cell, "table:number-columns-repeated"
121        )
122        self._rmap = make_cache_map(idx_repeated_seq)
123
124    # Public API
125
126    @property
127    def clone(self) -> Row:
128        clone = Element.clone.fget(self)  # type: ignore
129        clone.y = self.y
130        if hasattr(self, "_tmap"):
131            if hasattr(self, "_rmap"):
132                clone._rmap = self._rmap[:]
133            clone._tmap = self._tmap[:]
134            clone._cmap = self._cmap[:]
135        return clone
136
137    def _set_repeated(self, repeated: int | None) -> None:
138        """Internal only. Set the numnber of times the row is repeated, or
139        None to delete it. Without changing cache.
140
141        Arguments:
142
143            repeated -- int
144        """
145        if repeated is None or repeated < 2:
146            with contextlib.suppress(KeyError):
147                self.del_attribute("table:number-rows-repeated")
148            return
149        self.set_attribute("table:number-rows-repeated", str(repeated))
150
151    @property
152    def repeated(self) -> int | None:
153        """Get / set the number of times the row is repeated.
154
155        Always None when using the table API.
156
157        Return: int or None
158        """
159        repeated = self.get_attribute("table:number-rows-repeated")
160        if repeated is None:
161            return None
162        return int(repeated)
163
164    @repeated.setter
165    def repeated(self, repeated: int | None) -> None:
166        self._set_repeated(repeated)
167        # update cache
168        current: Element = self
169        while True:
170            # look for Table, parent may be group of rows
171            upper = current.parent
172            if not upper:
173                # lonely row
174                return
175            # parent may be group of rows, not table
176            if isinstance(upper, Element) and upper._tag == "table:table":
177                break
178            current = upper
179        # fixme : need to optimize this
180        if isinstance(upper, Element) and upper._tag == "table:table":
181            upper._compute_table_cache()
182            if hasattr(self, "_tmap"):
183                del self._tmap[:]
184                self._tmap.extend(upper._tmap)
185            else:
186                self._tmap = upper._tmap
187
188    @property
189    def style(self) -> str | None:
190        """Get /set the style of the row itself.
191
192        Return: str
193        """
194        return self.get_attribute("table:style-name")  # type: ignore
195
196    @style.setter
197    def style(self, style: str | Element) -> None:
198        self.set_style_attribute("table:style-name", style)
199
200    @property
201    def width(self) -> int:
202        """Get the number of expected cells in the row, i.e. addition
203        repetitions.
204
205        Return: int
206        """
207        try:
208            value = self._rmap[-1] + 1
209        except Exception:
210            value = 0
211        return value
212
213    def _translate_x_from_any(self, x: str | int) -> int:
214        return translate_from_any(x, self.width, 0)
215
216    def traverse(  # noqa: C901
217        self,
218        start: int | None = None,
219        end: int | None = None,
220    ) -> Iterator[Cell]:
221        """Yield as many cell elements as expected cells in the row, i.e.
222        expand repetitions by returning the same cell as many times as
223        necessary.
224
225            Arguments:
226
227                start -- int
228
229                end -- int
230
231        Copies are returned, use set_cell() to push them back.
232        """
233        idx = -1
234        before = -1
235        x = 0
236        cell: Cell
237        if start is None and end is None:
238            for juska in self._rmap:
239                idx += 1
240                if idx in self._indexes["_rmap"]:
241                    cell = self._indexes["_rmap"][idx]
242                else:
243                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
244                    if not isinstance(cell, Cell):
245                        raise TypeError(f"Not a cell: {cell!r}")
246                    self._indexes["_rmap"][idx] = cell
247                repeated = juska - before
248                before = juska
249                for _i in range(repeated or 1):
250                    # Return a copy without the now obsolete repetition
251                    if cell is None:
252                        cell = Cell()
253                    else:
254                        cell = cell.clone
255                        if repeated > 1:
256                            cell.repeated = None
257                    cell.y = self.y
258                    cell.x = x
259                    x += 1
260                    yield cell
261        else:
262            if start is None:
263                start = 0
264            start = max(0, start)
265            if end is None:
266                try:
267                    end = self._rmap[-1]
268                except Exception:
269                    end = -1
270            start_map = find_odf_idx(self._rmap, start)
271            if start_map is None:
272                return
273            if start_map > 0:
274                before = self._rmap[start_map - 1]
275            idx = start_map - 1
276            before = start - 1
277            x = start
278            for juska in self._rmap[start_map:]:
279                idx += 1
280                if idx in self._indexes["_rmap"]:
281                    cell = self._indexes["_rmap"][idx]
282                else:
283                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
284                    if not isinstance(cell, Cell):
285                        raise TypeError(f"Not a cell: {cell!r}")
286                    self._indexes["_rmap"][idx] = cell
287                repeated = juska - before
288                before = juska
289                for _i in range(repeated or 1):
290                    if x <= end:
291                        if cell is None:
292                            cell = Cell()
293                        else:
294                            cell = cell.clone
295                            if repeated > 1 or (x == start and start > 0):
296                                cell.repeated = None
297                        cell.y = self.y
298                        cell.x = x
299                        x += 1
300                        yield cell
301
302    def get_cells(
303        self,
304        coord: str | tuple | None = None,
305        style: str | None = None,
306        content: str | None = None,
307        cell_type: str | None = None,
308    ) -> list[Cell]:
309        """Get the list of cells matching the criteria.
310
311        Filter by cell_type, with cell_type 'all' will retrieve cells of any
312        type, aka non empty cells.
313
314        Filter by coordinates will retrieve the amount of cells defined by
315        'coord', minus the other filters.
316
317        Arguments:
318
319            coord -- str or tuple of int : coordinates
320
321            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
322                         'currency', 'percentage' or 'all'
323
324            content -- str regex
325
326            style -- str
327
328        Return: list of Cell
329        """
330        # fixme : not clones ?
331        if coord:
332            x, z = self._translate_row_coordinates(coord)
333        else:
334            x = None
335            z = None
336        if cell_type:
337            cell_type = cell_type.lower().strip()
338        cells: list[Cell] = []
339        for cell in self.traverse(start=x, end=z):
340            # Filter the cells by cell_type
341            if cell_type:
342                ctype = cell.type
343                if not ctype or not (ctype == cell_type or cell_type == "all"):
344                    continue
345            # Filter the cells with the regex
346            if content and not cell.match(content):
347                continue
348            # Filter the cells with the style
349            if style and style != cell.style:
350                continue
351            cells.append(cell)
352        return cells
353
354    def _get_cell2(self, x: int, clone: bool = True) -> Cell | None:
355        if x >= self.width:
356            return Cell()
357        if clone:
358            return self._get_cell2_base(x).clone  # type: ignore
359        else:
360            return self._get_cell2_base(x)
361
362    def _get_cell2_base(self, x: int) -> Cell | None:
363        idx = find_odf_idx(self._rmap, x)
364        cell: Cell
365        if idx is not None:
366            if idx in self._indexes["_rmap"]:
367                cell = self._indexes["_rmap"][idx]
368            else:
369                cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
370                self._indexes["_rmap"][idx] = cell
371            return cell
372        return None
373
374    def get_cell(self, x: int, clone: bool = True) -> Cell | None:
375        """Get the cell at position "x" starting from 0. Alphabetical
376        positions like "D" are accepted.
377
378        A  copy is returned, use set_cell() to push it back.
379
380        Arguments:
381
382            x -- int or str
383
384        Return: Cell | None
385        """
386        x = self._translate_x_from_any(x)
387        cell = self._get_cell2(x, clone=clone)
388        if not cell:
389            return None
390        cell.y = self.y
391        cell.x = x
392        return cell
393
394    def get_value(
395        self,
396        x: int | str,
397        get_type: bool = False,
398    ) -> Any | tuple[Any, str]:
399        """Shortcut to get the value of the cell at position "x".
400        If get_type is True, returns the tuples (value, ODF type).
401
402        If the cell is empty, returns None or (None, None)
403
404        See get_cell() and Cell.get_value().
405        """
406        if get_type:
407            x = self._translate_x_from_any(x)
408            cell = self._get_cell2_base(x)
409            if cell is None:
410                return (None, None)
411            return cell.get_value(get_type=get_type)
412        x = self._translate_x_from_any(x)
413        cell = self._get_cell2_base(x)
414        if cell is None:
415            return None
416        return cell.get_value()
417
418    def set_cell(
419        self,
420        x: int | str,
421        cell: Cell | None = None,
422        clone: bool = True,
423    ) -> Cell:
424        """Push the cell back in the row at position "x" starting from 0.
425        Alphabetical positions like "D" are accepted.
426
427        Arguments:
428
429            x -- int or str
430
431        returns the cell with x and y updated
432        """
433        cell_back: Cell
434        if cell is None:
435            cell = Cell()
436            repeated = 1
437            clone = False
438        else:
439            repeated = cell.repeated or 1
440        x = self._translate_x_from_any(x)
441        # Outside the defined row
442        diff = x - self.width
443        if diff == 0:
444            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
445        elif diff > 0:
446            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
447            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
448        else:
449            # Inside the defined row
450            set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone)
451            cell.x = x
452            cell.y = self.y
453            cell_back = cell
454        return cell_back
455
456    def set_value(
457        self,
458        x: int | str,
459        value: Any,
460        style: str | None = None,
461        cell_type: str | None = None,
462        currency: str | None = None,
463    ) -> None:
464        """Shortcut to set the value of the cell at position "x".
465
466        Arguments:
467
468            x -- int or str
469
470            value -- Python type
471
472            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
473                     'string' or 'time'
474
475            currency -- three-letter str
476
477            style -- str
478
479        See get_cell() and Cell.get_value().
480        """
481        self.set_cell(
482            x,
483            Cell(value, style=style, cell_type=cell_type, currency=currency),
484            clone=False,
485        )
486
487    def insert_cell(
488        self,
489        x: int | str,
490        cell: Cell | None = None,
491        clone: bool = True,
492    ) -> Cell:
493        """Insert the given cell at position "x" starting from 0. If no cell
494        is given, an empty one is created.
495
496        Alphabetical positions like "D" are accepted.
497
498        Do not use when working on a table, use Table.insert_cell().
499
500        Arguments:
501
502            x -- int or str
503
504            cell -- Cell
505
506        returns the cell with x and y updated
507        """
508        cell_back: Cell
509        if cell is None:
510            cell = Cell()
511        x = self._translate_x_from_any(x)
512        # Outside the defined row
513        diff = x - self.width
514        if diff < 0:
515            insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap")
516            cell.x = x
517            cell.y = self.y
518            cell_back = cell
519        elif diff == 0:
520            cell_back = self.append_cell(cell, clone=clone)
521        else:
522            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
523            cell_back = self.append_cell(cell, clone=clone)
524        return cell_back
525
526    def extend_cells(self, cells: Iterable[Cell] | None = None) -> None:
527        if cells is None:
528            cells = []
529        self.extend(cells)
530        self._compute_row_cache()
531
532    def append_cell(
533        self,
534        cell: Cell | None = None,
535        clone: bool = True,
536        _repeated: int | None = None,
537    ) -> Cell:
538        """Append the given cell at the end of the row. Repeated cells are
539        accepted. If no cell is given, an empty one is created.
540
541        Do not use when working on a table, use Table.append_cell().
542
543        Arguments:
544
545            cell -- Cell
546
547            _repeated -- (optional), repeated value of the row
548
549        returns the cell with x and y updated
550        """
551        if cell is None:
552            cell = Cell()
553            clone = False
554        if clone:
555            cell = cell.clone
556        self._append(cell)
557        if _repeated is None:
558            _repeated = cell.repeated or 1
559        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
560        cell.x = self.width - 1
561        cell.y = self.y
562        return cell
563
564    # fix for unit test and typos
565    append = append_cell  # type: ignore
566
567    def delete_cell(self, x: int | str) -> None:
568        """Delete the cell at the given position "x" starting from 0.
569        Alphabetical positions like "D" are accepted.
570
571        Cells on the right will be shifted to the left. In a table, other
572        rows remain unaffected.
573
574        Arguments:
575
576            x -- int or str
577        """
578        x = self._translate_x_from_any(x)
579        if x >= self.width:
580            return
581        delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")
582
583    def get_values(
584        self,
585        coord: str | tuple | None = None,
586        cell_type: str | None = None,
587        complete: bool = False,
588        get_type: bool = False,
589    ) -> list[Any | tuple[Any, Any]]:
590        """Shortcut to get the cell values in this row.
591
592        Filter by cell_type, with cell_type 'all' will retrieve cells of any
593        type, aka non empty cells.
594        If cell_type is used and complete is True, missing values are
595        replaced by None.
596        If cell_type is None, complete is always True : with no cell type
597        queried, get_values() returns None for each empty cell, the length
598        of the list is equal to the length of the row (depending on
599        coordinates use).
600
601        If get_type is True, returns a tuple (value, ODF type of value), or
602        (None, None) for empty cells if complete is True.
603
604        Filter by coordinates will retrieve the amount of cells defined by
605        coordinates with None for empty cells, except when using cell_type.
606
607
608        Arguments:
609
610            coord -- str or tuple of int : coordinates in row
611
612            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
613                         'currency', 'percentage' or 'all'
614
615            complete -- boolean
616
617            get_type -- boolean
618
619        Return: list of Python types, or list of tuples.
620        """
621        if coord:
622            x, z = self._translate_row_coordinates(coord)
623        else:
624            x = None
625            z = None
626        if cell_type:
627            cell_type = cell_type.lower().strip()
628            values: list[Any | tuple[Any, Any]] = []
629            for cell in self.traverse(start=x, end=z):
630                # Filter the cells by cell_type
631                ctype = cell.type
632                if not ctype or not (ctype == cell_type or cell_type == "all"):
633                    if complete:
634                        if get_type:
635                            values.append((None, None))
636                        else:
637                            values.append(None)
638                    continue
639                values.append(cell.get_value(get_type=get_type))
640            return values
641        else:
642            return [
643                cell.get_value(get_type=get_type)
644                for cell in self.traverse(start=x, end=z)
645            ]
646
647    def set_cells(
648        self,
649        cells: list[Cell] | tuple[Cell] | None = None,
650        start: int | str = 0,
651        clone: bool = True,
652    ) -> None:
653        """Set the cells in the row, from the 'start' column.
654        This method does not clear the row, use row.clear() before to start
655        with an empty row.
656
657        Arguments:
658
659            cells -- list of cells
660
661            start -- int or str
662        """
663        if cells is None:
664            cells = []
665        if start is None:
666            start = 0
667        else:
668            start = self._translate_x_from_any(start)
669        if start == 0 and clone is False and (len(cells) >= self.width):
670            self.clear()
671            self.extend_cells(cells)
672        else:
673            x = start
674            for cell in cells:
675                self.set_cell(x, cell, clone=clone)
676                if cell:
677                    x += cell.repeated or 1
678                else:
679                    x += 1
680
681    def set_values(
682        self,
683        values: list[Any],
684        start: int | str = 0,
685        style: str | None = None,
686        cell_type: str | None = None,
687        currency: str | None = None,
688    ) -> None:
689        """Shortcut to set the value of cells in the row, from the 'start'
690        column vith values.
691        This method does not clear the row, use row.clear() before to start
692        with an empty row.
693
694        Arguments:
695
696            values -- list of Python types
697
698            start -- int or str
699
700            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
701                         'currency' or 'percentage'
702
703            currency -- three-letter str
704
705            style -- cell style
706        """
707        # fixme : if values n, n+ are same, use repeat
708        if start is None:
709            start = 0
710        else:
711            start = self._translate_x_from_any(start)
712        if start == 0 and (len(values) >= self.width):
713            self.clear()
714            cells = [
715                Cell(value, style=style, cell_type=cell_type, currency=currency)
716                for value in values
717            ]
718            self.extend_cells(cells)
719        else:
720            x = start
721            for value in values:
722                self.set_cell(
723                    x,
724                    Cell(value, style=style, cell_type=cell_type, currency=currency),
725                    clone=False,
726                )
727                x += 1
728
729    def rstrip(self, aggressive: bool = False) -> None:
730        """Remove *in-place* empty cells at the right of the row. An empty
731        cell has no value but can have style. If "aggressive" is True, style
732        is ignored.
733
734        Arguments:
735
736            aggressive -- bool
737        """
738        for cell in reversed(self._get_cells()):
739            if not cell.is_empty(aggressive=aggressive):  # type: ignore
740                break
741            self.delete(cell)
742        self._compute_row_cache()
743        self._indexes["_rmap"] = {}
744
745    def is_empty(self, aggressive: bool = False) -> bool:
746        """Return whether every cell in the row has no value or the value
747        evaluates to False (empty string), and no style.
748
749        If aggressive is True, empty cells with style are considered empty.
750
751        Arguments:
752
753            aggressive -- bool
754
755        Return: bool
756        """
757        return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells())  # type: ignore

ODF table row "table:table-row"

Row( width: int | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
59    def __init__(
60        self,
61        width: int | None = None,
62        repeated: int | None = None,
63        style: str | None = None,
64        **kwargs: Any,
65    ) -> None:
66        """create a Row, optionally filled with "width" number of cells.
67
68        Rows contain cells, their number determine the number of columns.
69
70        You don't generally have to create rows by hand, use the Table API.
71
72        Arguments:
73
74            width -- int
75
76            repeated -- int
77
78            style -- str
79        """
80        super().__init__(**kwargs)
81        self.y = None
82        if not hasattr(self, "_indexes"):
83            self._indexes = {}
84            self._indexes["_rmap"] = {}
85        if not hasattr(self, "_rmap"):
86            self._compute_row_cache()
87            if not hasattr(self, "_tmap"):
88                self._tmap = []
89                self._cmap = []
90        if self._do_init:
91            if width is not None:
92                for _i in range(width):
93                    self.append(Cell())  # type:ignore
94            if repeated:
95                self.repeated = repeated
96            if style is not None:
97                self.style = style
98            self._compute_row_cache()

create a Row, optionally filled with "width" number of cells.

Rows contain cells, their number determine the number of columns.

You don't generally have to create rows by hand, use the Table API.

Arguments:

width -- int

repeated -- int

style -- str
y
clone: Row
126    @property
127    def clone(self) -> Row:
128        clone = Element.clone.fget(self)  # type: ignore
129        clone.y = self.y
130        if hasattr(self, "_tmap"):
131            if hasattr(self, "_rmap"):
132                clone._rmap = self._rmap[:]
133            clone._tmap = self._tmap[:]
134            clone._cmap = self._cmap[:]
135        return clone
repeated: int | None
151    @property
152    def repeated(self) -> int | None:
153        """Get / set the number of times the row is repeated.
154
155        Always None when using the table API.
156
157        Return: int or None
158        """
159        repeated = self.get_attribute("table:number-rows-repeated")
160        if repeated is None:
161            return None
162        return int(repeated)

Get / set the number of times the row is repeated.

Always None when using the table API.

Return: int or None

style: str | None
188    @property
189    def style(self) -> str | None:
190        """Get /set the style of the row itself.
191
192        Return: str
193        """
194        return self.get_attribute("table:style-name")  # type: ignore

Get /set the style of the row itself.

Return: str

width: int
200    @property
201    def width(self) -> int:
202        """Get the number of expected cells in the row, i.e. addition
203        repetitions.
204
205        Return: int
206        """
207        try:
208            value = self._rmap[-1] + 1
209        except Exception:
210            value = 0
211        return value

Get the number of expected cells in the row, i.e. addition repetitions.

Return: int

def traverse( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Cell]:
216    def traverse(  # noqa: C901
217        self,
218        start: int | None = None,
219        end: int | None = None,
220    ) -> Iterator[Cell]:
221        """Yield as many cell elements as expected cells in the row, i.e.
222        expand repetitions by returning the same cell as many times as
223        necessary.
224
225            Arguments:
226
227                start -- int
228
229                end -- int
230
231        Copies are returned, use set_cell() to push them back.
232        """
233        idx = -1
234        before = -1
235        x = 0
236        cell: Cell
237        if start is None and end is None:
238            for juska in self._rmap:
239                idx += 1
240                if idx in self._indexes["_rmap"]:
241                    cell = self._indexes["_rmap"][idx]
242                else:
243                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
244                    if not isinstance(cell, Cell):
245                        raise TypeError(f"Not a cell: {cell!r}")
246                    self._indexes["_rmap"][idx] = cell
247                repeated = juska - before
248                before = juska
249                for _i in range(repeated or 1):
250                    # Return a copy without the now obsolete repetition
251                    if cell is None:
252                        cell = Cell()
253                    else:
254                        cell = cell.clone
255                        if repeated > 1:
256                            cell.repeated = None
257                    cell.y = self.y
258                    cell.x = x
259                    x += 1
260                    yield cell
261        else:
262            if start is None:
263                start = 0
264            start = max(0, start)
265            if end is None:
266                try:
267                    end = self._rmap[-1]
268                except Exception:
269                    end = -1
270            start_map = find_odf_idx(self._rmap, start)
271            if start_map is None:
272                return
273            if start_map > 0:
274                before = self._rmap[start_map - 1]
275            idx = start_map - 1
276            before = start - 1
277            x = start
278            for juska in self._rmap[start_map:]:
279                idx += 1
280                if idx in self._indexes["_rmap"]:
281                    cell = self._indexes["_rmap"][idx]
282                else:
283                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
284                    if not isinstance(cell, Cell):
285                        raise TypeError(f"Not a cell: {cell!r}")
286                    self._indexes["_rmap"][idx] = cell
287                repeated = juska - before
288                before = juska
289                for _i in range(repeated or 1):
290                    if x <= end:
291                        if cell is None:
292                            cell = Cell()
293                        else:
294                            cell = cell.clone
295                            if repeated > 1 or (x == start and start > 0):
296                                cell.repeated = None
297                        cell.y = self.y
298                        cell.x = x
299                        x += 1
300                        yield cell

Yield as many cell elements as expected cells in the row, i.e. expand repetitions by returning the same cell as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_cell() to push them back.

def get_cells( self, coord: str | tuple | None = None, style: str | None = None, content: str | None = None, cell_type: str | None = None) -> list[Cell]:
302    def get_cells(
303        self,
304        coord: str | tuple | None = None,
305        style: str | None = None,
306        content: str | None = None,
307        cell_type: str | None = None,
308    ) -> list[Cell]:
309        """Get the list of cells matching the criteria.
310
311        Filter by cell_type, with cell_type 'all' will retrieve cells of any
312        type, aka non empty cells.
313
314        Filter by coordinates will retrieve the amount of cells defined by
315        'coord', minus the other filters.
316
317        Arguments:
318
319            coord -- str or tuple of int : coordinates
320
321            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
322                         'currency', 'percentage' or 'all'
323
324            content -- str regex
325
326            style -- str
327
328        Return: list of Cell
329        """
330        # fixme : not clones ?
331        if coord:
332            x, z = self._translate_row_coordinates(coord)
333        else:
334            x = None
335            z = None
336        if cell_type:
337            cell_type = cell_type.lower().strip()
338        cells: list[Cell] = []
339        for cell in self.traverse(start=x, end=z):
340            # Filter the cells by cell_type
341            if cell_type:
342                ctype = cell.type
343                if not ctype or not (ctype == cell_type or cell_type == "all"):
344                    continue
345            # Filter the cells with the regex
346            if content and not cell.match(content):
347                continue
348            # Filter the cells with the style
349            if style and style != cell.style:
350                continue
351            cells.append(cell)
352        return cells

Get the list of cells matching the criteria.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.

Filter by coordinates will retrieve the amount of cells defined by 'coord', minus the other filters.

Arguments:

coord -- str or tuple of int : coordinates

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

Return: list of Cell

def get_cell(self, x: int, clone: bool = True) -> Cell | None:
374    def get_cell(self, x: int, clone: bool = True) -> Cell | None:
375        """Get the cell at position "x" starting from 0. Alphabetical
376        positions like "D" are accepted.
377
378        A  copy is returned, use set_cell() to push it back.
379
380        Arguments:
381
382            x -- int or str
383
384        Return: Cell | None
385        """
386        x = self._translate_x_from_any(x)
387        cell = self._get_cell2(x, clone=clone)
388        if not cell:
389            return None
390        cell.y = self.y
391        cell.x = x
392        return cell

Get the cell at position "x" starting from 0. Alphabetical positions like "D" are accepted.

A copy is returned, use set_cell() to push it back.

Arguments:

x -- int or str

Return: Cell | None

def get_value( self, x: int | str, get_type: bool = False) -> typing.Any | tuple[typing.Any, str]:
394    def get_value(
395        self,
396        x: int | str,
397        get_type: bool = False,
398    ) -> Any | tuple[Any, str]:
399        """Shortcut to get the value of the cell at position "x".
400        If get_type is True, returns the tuples (value, ODF type).
401
402        If the cell is empty, returns None or (None, None)
403
404        See get_cell() and Cell.get_value().
405        """
406        if get_type:
407            x = self._translate_x_from_any(x)
408            cell = self._get_cell2_base(x)
409            if cell is None:
410                return (None, None)
411            return cell.get_value(get_type=get_type)
412        x = self._translate_x_from_any(x)
413        cell = self._get_cell2_base(x)
414        if cell is None:
415            return None
416        return cell.get_value()

Shortcut to get the value of the cell at position "x". If get_type is True, returns the tuples (value, ODF type).

If the cell is empty, returns None or (None, None)

See get_cell() and Cell.get_value().

def set_cell( self, x: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
418    def set_cell(
419        self,
420        x: int | str,
421        cell: Cell | None = None,
422        clone: bool = True,
423    ) -> Cell:
424        """Push the cell back in the row at position "x" starting from 0.
425        Alphabetical positions like "D" are accepted.
426
427        Arguments:
428
429            x -- int or str
430
431        returns the cell with x and y updated
432        """
433        cell_back: Cell
434        if cell is None:
435            cell = Cell()
436            repeated = 1
437            clone = False
438        else:
439            repeated = cell.repeated or 1
440        x = self._translate_x_from_any(x)
441        # Outside the defined row
442        diff = x - self.width
443        if diff == 0:
444            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
445        elif diff > 0:
446            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
447            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
448        else:
449            # Inside the defined row
450            set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone)
451            cell.x = x
452            cell.y = self.y
453            cell_back = cell
454        return cell_back

Push the cell back in the row at position "x" starting from 0. Alphabetical positions like "D" are accepted.

Arguments:

x -- int or str

returns the cell with x and y updated

def set_value( self, x: int | str, value: Any, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
456    def set_value(
457        self,
458        x: int | str,
459        value: Any,
460        style: str | None = None,
461        cell_type: str | None = None,
462        currency: str | None = None,
463    ) -> None:
464        """Shortcut to set the value of the cell at position "x".
465
466        Arguments:
467
468            x -- int or str
469
470            value -- Python type
471
472            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
473                     'string' or 'time'
474
475            currency -- three-letter str
476
477            style -- str
478
479        See get_cell() and Cell.get_value().
480        """
481        self.set_cell(
482            x,
483            Cell(value, style=style, cell_type=cell_type, currency=currency),
484            clone=False,
485        )

Shortcut to set the value of the cell at position "x".

Arguments:

x -- int or str

value -- Python type

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
         'string' or 'time'

currency -- three-letter str

style -- str

See get_cell() and Cell.get_value().

def insert_cell( self, x: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
487    def insert_cell(
488        self,
489        x: int | str,
490        cell: Cell | None = None,
491        clone: bool = True,
492    ) -> Cell:
493        """Insert the given cell at position "x" starting from 0. If no cell
494        is given, an empty one is created.
495
496        Alphabetical positions like "D" are accepted.
497
498        Do not use when working on a table, use Table.insert_cell().
499
500        Arguments:
501
502            x -- int or str
503
504            cell -- Cell
505
506        returns the cell with x and y updated
507        """
508        cell_back: Cell
509        if cell is None:
510            cell = Cell()
511        x = self._translate_x_from_any(x)
512        # Outside the defined row
513        diff = x - self.width
514        if diff < 0:
515            insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap")
516            cell.x = x
517            cell.y = self.y
518            cell_back = cell
519        elif diff == 0:
520            cell_back = self.append_cell(cell, clone=clone)
521        else:
522            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
523            cell_back = self.append_cell(cell, clone=clone)
524        return cell_back

Insert the given cell at position "x" starting from 0. If no cell is given, an empty one is created.

Alphabetical positions like "D" are accepted.

Do not use when working on a table, use Table.insert_cell().

Arguments:

x -- int or str

cell -- Cell

returns the cell with x and y updated

def extend_cells( self, cells: collections.abc.Iterable[Cell] | None = None) -> None:
526    def extend_cells(self, cells: Iterable[Cell] | None = None) -> None:
527        if cells is None:
528            cells = []
529        self.extend(cells)
530        self._compute_row_cache()
def append_cell( self, cell: Cell | None = None, clone: bool = True, _repeated: int | None = None) -> Cell:
532    def append_cell(
533        self,
534        cell: Cell | None = None,
535        clone: bool = True,
536        _repeated: int | None = None,
537    ) -> Cell:
538        """Append the given cell at the end of the row. Repeated cells are
539        accepted. If no cell is given, an empty one is created.
540
541        Do not use when working on a table, use Table.append_cell().
542
543        Arguments:
544
545            cell -- Cell
546
547            _repeated -- (optional), repeated value of the row
548
549        returns the cell with x and y updated
550        """
551        if cell is None:
552            cell = Cell()
553            clone = False
554        if clone:
555            cell = cell.clone
556        self._append(cell)
557        if _repeated is None:
558            _repeated = cell.repeated or 1
559        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
560        cell.x = self.width - 1
561        cell.y = self.y
562        return cell

Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.

Do not use when working on a table, use Table.append_cell().

Arguments:

cell -- Cell

_repeated -- (optional), repeated value of the row

returns the cell with x and y updated

def append( self, cell: Cell | None = None, clone: bool = True, _repeated: int | None = None) -> Cell:
532    def append_cell(
533        self,
534        cell: Cell | None = None,
535        clone: bool = True,
536        _repeated: int | None = None,
537    ) -> Cell:
538        """Append the given cell at the end of the row. Repeated cells are
539        accepted. If no cell is given, an empty one is created.
540
541        Do not use when working on a table, use Table.append_cell().
542
543        Arguments:
544
545            cell -- Cell
546
547            _repeated -- (optional), repeated value of the row
548
549        returns the cell with x and y updated
550        """
551        if cell is None:
552            cell = Cell()
553            clone = False
554        if clone:
555            cell = cell.clone
556        self._append(cell)
557        if _repeated is None:
558            _repeated = cell.repeated or 1
559        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
560        cell.x = self.width - 1
561        cell.y = self.y
562        return cell

Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.

Do not use when working on a table, use Table.append_cell().

Arguments:

cell -- Cell

_repeated -- (optional), repeated value of the row

returns the cell with x and y updated

def delete_cell(self, x: int | str) -> None:
567    def delete_cell(self, x: int | str) -> None:
568        """Delete the cell at the given position "x" starting from 0.
569        Alphabetical positions like "D" are accepted.
570
571        Cells on the right will be shifted to the left. In a table, other
572        rows remain unaffected.
573
574        Arguments:
575
576            x -- int or str
577        """
578        x = self._translate_x_from_any(x)
579        if x >= self.width:
580            return
581        delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")

Delete the cell at the given position "x" starting from 0. Alphabetical positions like "D" are accepted.

Cells on the right will be shifted to the left. In a table, other rows remain unaffected.

Arguments:

x -- int or str
def get_values( self, coord: str | tuple | None = None, cell_type: str | None = None, complete: bool = False, get_type: bool = False) -> list[typing.Any | tuple[typing.Any, typing.Any]]:
583    def get_values(
584        self,
585        coord: str | tuple | None = None,
586        cell_type: str | None = None,
587        complete: bool = False,
588        get_type: bool = False,
589    ) -> list[Any | tuple[Any, Any]]:
590        """Shortcut to get the cell values in this row.
591
592        Filter by cell_type, with cell_type 'all' will retrieve cells of any
593        type, aka non empty cells.
594        If cell_type is used and complete is True, missing values are
595        replaced by None.
596        If cell_type is None, complete is always True : with no cell type
597        queried, get_values() returns None for each empty cell, the length
598        of the list is equal to the length of the row (depending on
599        coordinates use).
600
601        If get_type is True, returns a tuple (value, ODF type of value), or
602        (None, None) for empty cells if complete is True.
603
604        Filter by coordinates will retrieve the amount of cells defined by
605        coordinates with None for empty cells, except when using cell_type.
606
607
608        Arguments:
609
610            coord -- str or tuple of int : coordinates in row
611
612            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
613                         'currency', 'percentage' or 'all'
614
615            complete -- boolean
616
617            get_type -- boolean
618
619        Return: list of Python types, or list of tuples.
620        """
621        if coord:
622            x, z = self._translate_row_coordinates(coord)
623        else:
624            x = None
625            z = None
626        if cell_type:
627            cell_type = cell_type.lower().strip()
628            values: list[Any | tuple[Any, Any]] = []
629            for cell in self.traverse(start=x, end=z):
630                # Filter the cells by cell_type
631                ctype = cell.type
632                if not ctype or not (ctype == cell_type or cell_type == "all"):
633                    if complete:
634                        if get_type:
635                            values.append((None, None))
636                        else:
637                            values.append(None)
638                    continue
639                values.append(cell.get_value(get_type=get_type))
640            return values
641        else:
642            return [
643                cell.get_value(get_type=get_type)
644                for cell in self.traverse(start=x, end=z)
645            ]

Shortcut to get the cell values in this row.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type is used and complete is True, missing values are replaced by None. If cell_type is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length of the list is equal to the length of the row (depending on coordinates use).

If get_type is True, returns a tuple (value, ODF type of value), or (None, None) for empty cells if complete is True.

Filter by coordinates will retrieve the amount of cells defined by coordinates with None for empty cells, except when using cell_type.

Arguments:

coord -- str or tuple of int : coordinates in row

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of Python types, or list of tuples.

def set_cells( self, cells: list[Cell] | tuple[Cell] | None = None, start: int | str = 0, clone: bool = True) -> None:
647    def set_cells(
648        self,
649        cells: list[Cell] | tuple[Cell] | None = None,
650        start: int | str = 0,
651        clone: bool = True,
652    ) -> None:
653        """Set the cells in the row, from the 'start' column.
654        This method does not clear the row, use row.clear() before to start
655        with an empty row.
656
657        Arguments:
658
659            cells -- list of cells
660
661            start -- int or str
662        """
663        if cells is None:
664            cells = []
665        if start is None:
666            start = 0
667        else:
668            start = self._translate_x_from_any(start)
669        if start == 0 and clone is False and (len(cells) >= self.width):
670            self.clear()
671            self.extend_cells(cells)
672        else:
673            x = start
674            for cell in cells:
675                self.set_cell(x, cell, clone=clone)
676                if cell:
677                    x += cell.repeated or 1
678                else:
679                    x += 1

Set the cells in the row, from the 'start' column. This method does not clear the row, use row.clear() before to start with an empty row.

Arguments:

cells -- list of cells

start -- int or str
def set_values( self, values: list[typing.Any], start: int | str = 0, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
681    def set_values(
682        self,
683        values: list[Any],
684        start: int | str = 0,
685        style: str | None = None,
686        cell_type: str | None = None,
687        currency: str | None = None,
688    ) -> None:
689        """Shortcut to set the value of cells in the row, from the 'start'
690        column vith values.
691        This method does not clear the row, use row.clear() before to start
692        with an empty row.
693
694        Arguments:
695
696            values -- list of Python types
697
698            start -- int or str
699
700            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
701                         'currency' or 'percentage'
702
703            currency -- three-letter str
704
705            style -- cell style
706        """
707        # fixme : if values n, n+ are same, use repeat
708        if start is None:
709            start = 0
710        else:
711            start = self._translate_x_from_any(start)
712        if start == 0 and (len(values) >= self.width):
713            self.clear()
714            cells = [
715                Cell(value, style=style, cell_type=cell_type, currency=currency)
716                for value in values
717            ]
718            self.extend_cells(cells)
719        else:
720            x = start
721            for value in values:
722                self.set_cell(
723                    x,
724                    Cell(value, style=style, cell_type=cell_type, currency=currency),
725                    clone=False,
726                )
727                x += 1

Shortcut to set the value of cells in the row, from the 'start' column vith values. This method does not clear the row, use row.clear() before to start with an empty row.

Arguments:

values -- list of Python types

start -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency' or 'percentage'

currency -- three-letter str

style -- cell style
def rstrip(self, aggressive: bool = False) -> None:
729    def rstrip(self, aggressive: bool = False) -> None:
730        """Remove *in-place* empty cells at the right of the row. An empty
731        cell has no value but can have style. If "aggressive" is True, style
732        is ignored.
733
734        Arguments:
735
736            aggressive -- bool
737        """
738        for cell in reversed(self._get_cells()):
739            if not cell.is_empty(aggressive=aggressive):  # type: ignore
740                break
741            self.delete(cell)
742        self._compute_row_cache()
743        self._indexes["_rmap"] = {}

Remove in-place empty cells at the right of the row. An empty cell has no value but can have style. If "aggressive" is True, style is ignored.

Arguments:

aggressive -- bool
def is_empty(self, aggressive: bool = False) -> bool:
745    def is_empty(self, aggressive: bool = False) -> bool:
746        """Return whether every cell in the row has no value or the value
747        evaluates to False (empty string), and no style.
748
749        If aggressive is True, empty cells with style are considered empty.
750
751        Arguments:
752
753            aggressive -- bool
754
755        Return: bool
756        """
757        return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells())  # type: ignore

Return whether every cell in the row has no value or the value evaluates to False (empty string), and no style.

If aggressive is True, empty cells with style are considered empty.

Arguments:

aggressive -- bool

Return: bool

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class RowGroup(odfdo.Element):
133class RowGroup(Element):
134    """ "table:table-row-group" group rows with common properties."""
135
136    # TODO
137    _tag = "table:table-row-group"
138    _caching = True
139
140    def __init__(
141        self,
142        height: int | None = None,
143        width: int | None = None,
144        **kwargs: Any,
145    ) -> None:
146        """Create a group of rows, optionnaly filled with "height" number of
147        rows, of "width" cells each.
148
149        Row group bear style information applied to a series of rows.
150
151        Arguments:
152
153            height -- int
154
155            width -- int
156        """
157        super().__init__(**kwargs)
158        if self._do_init and height is not None:
159            for _i in range(height):
160                row = Row(width=width)
161                self.append(row)

"table:table-row-group" group rows with common properties.

RowGroup(height: int | None = None, width: int | None = None, **kwargs: Any)
140    def __init__(
141        self,
142        height: int | None = None,
143        width: int | None = None,
144        **kwargs: Any,
145    ) -> None:
146        """Create a group of rows, optionnaly filled with "height" number of
147        rows, of "width" cells each.
148
149        Row group bear style information applied to a series of rows.
150
151        Arguments:
152
153            height -- int
154
155            width -- int
156        """
157        super().__init__(**kwargs)
158        if self._do_init and height is not None:
159            for _i in range(height):
160                row = Row(width=width)
161                self.append(row)

Create a group of rows, optionnaly filled with "height" number of rows, of "width" cells each.

Row group bear style information applied to a series of rows.

Arguments:

height -- int

width -- int
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Section(odfdo.Element):
32class Section(Element):
33    """ODF section "text:section"
34
35    Arguments:
36
37        style -- str
38
39        name -- str
40    """
41
42    _tag = "text:section"
43    _properties = (
44        PropDef("style", "text:style-name"),
45        PropDef("name", "text:name"),
46    )
47
48    def __init__(
49        self,
50        style: str | None = None,
51        name: str | None = None,
52        **kwargs: Any,
53    ) -> None:
54        super().__init__(**kwargs)
55        if self._do_init:
56            if style:
57                self.style = style
58            if name:
59                self.name = name
60
61    def get_formatted_text(self, context: dict | None = None) -> str:
62        result = [element.get_formatted_text(context) for element in self.children]
63        result.append("\n")
64        return "".join(result)

ODF section "text:section"

Arguments:

style -- str

name -- str
Section(style: str | None = None, name: str | None = None, **kwargs: Any)
48    def __init__(
49        self,
50        style: str | None = None,
51        name: str | None = None,
52        **kwargs: Any,
53    ) -> None:
54        super().__init__(**kwargs)
55        if self._do_init:
56            if style:
57                self.style = style
58            if name:
59                self.name = name
def get_formatted_text(self, context: dict | None = None) -> str:
61    def get_formatted_text(self, context: dict | None = None) -> str:
62        result = [element.get_formatted_text(context) for element in self.children]
63        result.append("\n")
64        return "".join(result)

This function should return a beautiful version of the text.

style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Spacer(odfdo.Element):
169class Spacer(Element):
170    """This element shall be used to represent the second and all following “ “
171    (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters.
172    Note: It is not an error if the character preceding the element is not a
173    white space character, but it is good practice to use this element only for
174    the second and all following SPACE characters in a sequence.
175    """
176
177    _tag = "text:s"
178    _properties: tuple[PropDef, ...] = (PropDef("number", "text:c"),)
179
180    def __init__(self, number: int = 1, **kwargs: Any):
181        """
182        Arguments:
183
184            number -- int
185        """
186        super().__init__(**kwargs)
187        if self._do_init:
188            self.number = str(number)

This element shall be used to represent the second and all following “ “ (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. Note: It is not an error if the character preceding the element is not a white space character, but it is good practice to use this element only for the second and all following SPACE characters in a sequence.

Spacer(number: int = 1, **kwargs: Any)
180    def __init__(self, number: int = 1, **kwargs: Any):
181        """
182        Arguments:
183
184            number -- int
185        """
186        super().__init__(**kwargs)
187        if self._do_init:
188            self.number = str(number)

Arguments:

number -- int
number: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Span(odfdo.Paragraph):
799class Span(Paragraph):
800    """Create a span element "text:span" of the given style containing the optional
801    given text.
802    """
803
804    _tag = "text:span"
805    _properties = (
806        PropDef("style", "text:style-name"),
807        PropDef("class_names", "text:class-names"),
808    )
809
810    def __init__(
811        self,
812        text: str | None = None,
813        style: str | None = None,
814        **kwargs: Any,
815    ) -> None:
816        """
817        Arguments:
818
819            text -- str
820
821            style -- str
822        """
823        super().__init__(**kwargs)
824        if self._do_init:
825            if text:
826                self.text = text
827            if style:
828                self.style = style

Create a span element "text:span" of the given style containing the optional given text.

Span(text: str | None = None, style: str | None = None, **kwargs: Any)
810    def __init__(
811        self,
812        text: str | None = None,
813        style: str | None = None,
814        **kwargs: Any,
815    ) -> None:
816        """
817        Arguments:
818
819            text -- str
820
821            style -- str
822        """
823        super().__init__(**kwargs)
824        if self._do_init:
825            if text:
826                self.text = text
827            if style:
828                self.style = style

Arguments:

text -- str

style -- str
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
class_names: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Paragraph
insert_note
insert_annotation
insert_annotation_end
set_reference_mark
set_reference_mark_end
insert_variable
set_span
remove_spans
remove_span
insert_reference
set_bookmark
odfdo.paragraph_base.ParagraphBase
get_formatted_text
append_plain_text
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Style(odfdo.Element):
 340class Style(Element):
 341    """Style class for all these tags:
 342
 343    'style:style'
 344    'number:date-style',
 345    'number:number-style',
 346    'number:percentage-style',
 347    'number:time-style'
 348    'style:font-face',
 349    'style:master-page',
 350    'style:page-layout',
 351    'style:presentation-page-layout',
 352    'text:list-style',
 353    'text:outline-style',
 354    'style:tab-stops',
 355    ...
 356    """
 357
 358    _properties: tuple[PropDef, ...] = (
 359        PropDef("page_layout", "style:page-layout-name", "master-page"),
 360        PropDef("next_style", "style:next-style-name", "master-page"),
 361        PropDef("name", "style:name"),
 362        PropDef("parent_style", "style:parent-style-name"),
 363        PropDef("display_name", "style:display-name"),
 364        PropDef("svg_font_family", "svg:font-family"),
 365        PropDef("font_family_generic", "style:font-family-generic"),
 366        PropDef("font_pitch", "style:font-pitch"),
 367        PropDef("text_style", "text:style-name"),
 368        PropDef("master_page", "style:master-page-name", "paragraph"),
 369        PropDef("master_page", "style:master-page-name", "paragraph"),
 370        PropDef("master_page", "style:master-page-name", "paragraph"),
 371        # style:tab-stop
 372        PropDef("style_type", "style:type"),
 373        PropDef("leader_style", "style:leader-style"),
 374        PropDef("leader_text", "style:leader-text"),
 375        PropDef("style_position", "style:position"),
 376        PropDef("leader_text", "style:position"),
 377    )
 378
 379    def __init__(  # noqa: C901
 380        self,
 381        family: str | None = None,
 382        name: str | None = None,
 383        display_name: str | None = None,
 384        parent_style: str | None = None,
 385        # Where properties apply
 386        area: str | None = None,
 387        # For family 'text':
 388        color: str | tuple | None = None,
 389        background_color: str | tuple | None = None,
 390        italic: bool = False,
 391        bold: bool = False,
 392        # For family 'paragraph'
 393        master_page: str | None = None,
 394        # For family 'master-page'
 395        page_layout: str | None = None,
 396        next_style: str | None = None,
 397        # For family 'table-cell'
 398        data_style: str | None = None,  # unused
 399        border: str | None = None,
 400        border_top: str | None = None,
 401        border_right: str | None = None,
 402        border_bottom: str | None = None,
 403        border_left: str | None = None,
 404        padding: str | None = None,
 405        padding_top: str | None = None,
 406        padding_bottom: str | None = None,
 407        padding_left: str | None = None,
 408        padding_right: str | None = None,
 409        shadow: str | None = None,
 410        # For family 'table-row'
 411        height: str | None = None,
 412        use_optimal_height: bool = False,
 413        # For family 'table-column'
 414        width: str | None = None,
 415        break_before: str | None = None,
 416        break_after: str | None = None,
 417        # For family 'graphic'
 418        min_height: str | None = None,
 419        # For family 'font-face'
 420        font_name: str | None = None,
 421        font_family: str | None = None,
 422        font_family_generic: str | None = None,
 423        font_pitch: str = "variable",
 424        # Every other property
 425        **kwargs: Any,
 426    ) -> None:
 427        """Create a style of the given family. The name is not mandatory at this
 428        point but will become required when inserting in a document as a common
 429        style.
 430
 431        The display name is the name the user sees in an office application.
 432
 433        The parent_style is the name of the style this style will inherit from.
 434
 435        To set properties, pass them as keyword arguments. The area properties
 436        apply to is optional and defaults to the family.
 437
 438        Arguments:
 439
 440            family -- 'paragraph', 'text', 'section', 'table', 'table-column',
 441                      'table-row', 'table-cell', 'table-page', 'chart',
 442                      'drawing-page', 'graphic', 'presentation',
 443                      'control', 'ruby', 'list', 'number', 'page-layout'
 444                      'font-face', or 'master-page'
 445
 446            name -- str
 447
 448            display_name -- str
 449
 450            parent_style -- str
 451
 452            area -- str
 453
 454        'text' Properties:
 455
 456            italic -- bool
 457
 458            bold -- bool
 459
 460        'paragraph' Properties:
 461
 462            master_page -- str
 463
 464        'master-page' Properties:
 465
 466            page_layout -- str
 467
 468            next_style -- str
 469
 470        'table-cell' Properties:
 471
 472            border, border_top, border_right, border_bottom, border_left -- str,
 473            e.g. "0.002cm solid #000000" or 'none'
 474
 475            padding, padding_top, padding_right, padding_bottom, padding_left -- str,
 476            e.g. "0.002cm" or 'none'
 477
 478            shadow -- str, e.g. "#808080 0.176cm 0.176cm"
 479
 480        'table-row' Properties:
 481
 482            height -- str, e.g. '5cm'
 483
 484            use_optimal_height -- bool
 485
 486        'table-column' Properties:
 487
 488            width -- str, e.g. '5cm'
 489
 490            break_before -- 'page', 'column' or 'auto'
 491
 492            break_after -- 'page', 'column' or 'auto'
 493        """
 494        self._family: str | None = None
 495        tag_or_elem = kwargs.get("tag_or_elem", None)
 496        if tag_or_elem is None:
 497            family = to_str(family)
 498            if family not in FAMILY_MAPPING:
 499                raise ValueError("Unknown family value: %s" % family)
 500            kwargs["tag"] = FAMILY_MAPPING[family]
 501        super().__init__(**kwargs)
 502        if self._do_init and family not in SUBCLASSED_STYLES:
 503            kwargs.pop("tag", None)
 504            kwargs.pop("tag_or_elem", None)
 505            self.family = family  # relevant test made by property
 506            # Common attributes
 507            if name:
 508                self.name = name
 509            if display_name:
 510                self.display_name = display_name
 511            if parent_style:
 512                self.parent_style = parent_style
 513            # Paragraph
 514            if family == "paragraph":
 515                if master_page:
 516                    self.master_page = master_page
 517            # Master Page
 518            elif family == "master-page":
 519                if page_layout:
 520                    self.page_layout = page_layout
 521                if next_style:
 522                    self.next_style = next_style
 523            # Font face
 524            elif family == "font-face":
 525                if not font_name:
 526                    raise ValueError("A font_name is required for 'font-face' style")
 527                self.set_font(
 528                    font_name,
 529                    family=font_family,
 530                    family_generic=font_family_generic,
 531                    pitch=font_pitch,
 532                )
 533            # Properties
 534            if area is None:
 535                area = family
 536            area = to_str(area)
 537            # Text
 538            if area == "text":
 539                if color:
 540                    kwargs["fo:color"] = color
 541                if background_color:
 542                    kwargs["fo:background-color"] = background_color
 543                if italic:
 544                    kwargs["fo:font-style"] = "italic"
 545                    kwargs["style:font-style-asian"] = "italic"
 546                    kwargs["style:font-style-complex"] = "italic"
 547                if bold:
 548                    kwargs["fo:font-weight"] = "bold"
 549                    kwargs["style:font-weight-asian"] = "bold"
 550                    kwargs["style:font-weight-complex"] = "bold"
 551            # Table cell
 552            elif area == "table-cell":
 553                if border:
 554                    kwargs["fo:border"] = border
 555                elif border_top or border_right or border_bottom or border_left:
 556                    kwargs["fo:border-top"] = border_top or "none"
 557                    kwargs["fo:border-right"] = border_right or "none"
 558                    kwargs["fo:border-bottom"] = border_bottom or "none"
 559                    kwargs["fo:border-left"] = border_left or "none"
 560                else:  # no border_top, ... neither border are defined
 561                    pass  # left untouched
 562                if padding:
 563                    kwargs["fo:padding"] = padding
 564                elif padding_top or padding_right or padding_bottom or padding_left:
 565                    kwargs["fo:padding-top"] = padding_top or "none"
 566                    kwargs["fo:padding-right"] = padding_right or "none"
 567                    kwargs["fo:padding-bottom"] = padding_bottom or "none"
 568                    kwargs["fo:padding-left"] = padding_left or "none"
 569                else:  # no border_top, ... neither border are defined
 570                    pass  # left untouched
 571                if shadow:
 572                    kwargs["style:shadow"] = shadow
 573                if background_color:
 574                    kwargs["fo:background-color"] = background_color
 575            # Table row
 576            elif area == "table-row":
 577                if height:
 578                    kwargs["style:row-height"] = height
 579                if use_optimal_height:
 580                    kwargs["style:use-optimal-row-height"] = Boolean.encode(
 581                        use_optimal_height
 582                    )
 583                if background_color:
 584                    kwargs["fo:background-color"] = background_color
 585            # Table column
 586            elif area == "table-column":
 587                if width:
 588                    kwargs["style:column-width"] = width
 589                if break_before:
 590                    kwargs["fo:break-before"] = break_before
 591                if break_after:
 592                    kwargs["fo:break-after"] = break_after
 593            # Graphic
 594            elif area == "graphic":
 595                if min_height:
 596                    kwargs["fo:min-height"] = min_height
 597            # Every other properties
 598            if kwargs:
 599                self.set_properties(kwargs, area=area)
 600
 601    @property
 602    def family(self) -> str | None:
 603        if self._family is None:
 604            self._family = FALSE_FAMILY_MAP_REVERSE.get(
 605                self.tag, self.get_attribute_string("style:family")
 606            )
 607        return self._family
 608
 609    @family.setter
 610    def family(self, family: str | None) -> None:
 611        self._family = family
 612        if family in FAMILY_ODF_STD and self.tag == "style:style":
 613            self.set_attribute("style:family", family)
 614
 615    def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
 616        """Get the mapping of all properties of this style. By default the
 617        properties of the same family, e.g. a paragraph style and its
 618        paragraph properties. Specify the area to get the text properties of
 619        a paragraph style for example.
 620
 621        Arguments:
 622
 623            area -- str
 624
 625        Return: dict
 626        """
 627        if area is None:
 628            area = self.family
 629        element = self.get_element(f"style:{area}-properties")
 630        if element is None:
 631            return None
 632        properties: dict[str, str | dict] = element.attributes  # type: ignore
 633        # Nested properties are nested dictionaries
 634        for child in element.children:
 635            properties[child.tag] = child.attributes
 636        return properties
 637
 638    def set_properties(  # noqa: C901
 639        self,
 640        properties: dict[str, str | dict] | None = None,
 641        style: Style | None = None,
 642        area: str | None = None,
 643        **kwargs: Any,
 644    ) -> None:
 645        """Set the properties of the "area" type of this style. Properties
 646        are given either as a dict or as named arguments (or both). The area
 647        is identical to the style family by default. If the properties
 648        element is missing, it is created.
 649
 650        Instead of properties, you can pass a style with properties of the
 651        same area. These will be copied.
 652
 653        Arguments:
 654
 655            properties -- dict
 656
 657            style -- Style
 658
 659            area -- 'paragraph', 'text'...
 660        """
 661        if properties is None:
 662            properties = {}
 663        if area is None:
 664            if isinstance(self.family, bool):
 665                area = None
 666            else:
 667                area = self.family
 668        element = self.get_element(f"style:{area}-properties")
 669        if element is None:
 670            element = Element.from_tag(f"style:{area}-properties")
 671            self.append(element)
 672        if properties or kwargs:
 673            properties = _expand_properties_dict(_merge_dicts(properties, kwargs))
 674        elif style is not None:
 675            properties = style.get_properties(area=area)
 676            if properties is None:
 677                return
 678        if properties is None:
 679            return
 680        for key, value in properties.items():
 681            if value is None:
 682                element.del_attribute(key)
 683            elif isinstance(value, (str, bool)):
 684                element.set_attribute(key, value)
 685            else:
 686                pass
 687
 688    def del_properties(
 689        self,
 690        properties: list[str] | None = None,
 691        area: str | None = None,
 692    ) -> None:
 693        """Delete the given properties, either by list argument or
 694        positional argument (or both). Remove only from the given area,
 695        identical to the style family by default.
 696
 697        Arguments:
 698
 699            properties -- list
 700
 701            area -- str
 702        """
 703        if properties is None:
 704            properties = []
 705        if area is None:
 706            area = self.family
 707        element = self.get_element(f"style:{area}-properties")
 708        if element is None:
 709            raise ValueError(
 710                f"properties element is inexistent for: style:{area}-properties"
 711            )
 712        for key in _expand_properties_list(properties):
 713            element.del_attribute(key)
 714
 715    def set_background(  # noqa: C901
 716        self,
 717        color: str | None = None,
 718        url: str | None = None,
 719        position: str | None = "center",
 720        repeat: str | None = None,
 721        opacity: str | None = None,
 722        filter: str | None = None,  # noqa: A002
 723    ) -> None:
 724        """Set the background color of a text style, or the background color
 725        or image of a paragraph style or page layout.
 726
 727        With no argument, remove any existing background.
 728
 729        The position is one or two of 'center', 'left', 'right', 'top' or
 730        'bottom'.
 731
 732        The repeat is 'no-repeat', 'repeat' or 'stretch'.
 733
 734        The opacity is a percentage integer (not a string with the '%s' sign)
 735
 736        The filter is an application-specific filter name defined elsewhere.
 737
 738        Though this method is defined on the base style class, it will raise
 739        an error if the style type is not compatible.
 740
 741        Arguments:
 742
 743            color -- '#rrggbb'
 744
 745            url -- str
 746
 747            position -- str
 748
 749            repeat -- str
 750
 751            opacity -- int
 752
 753            filter -- str
 754        """
 755        family = self.family
 756        if family not in {
 757            "text",
 758            "paragraph",
 759            "page-layout",
 760            "section",
 761            "table",
 762            "table-row",
 763            "table-cell",
 764            "graphic",
 765        }:
 766            raise TypeError("No background support for this family")
 767        if url is not None and family == "text":
 768            raise TypeError("No background image for text styles")
 769        properties = self.get_element(f"style:{family}-properties")
 770        bg_image: BackgroundImage | None = None
 771        if properties is not None:
 772            bg_image = properties.get_element("style:background-image")  # type:ignore
 773        # Erasing
 774        if color is None and url is None:
 775            if properties is None:
 776                return
 777            properties.del_attribute("fo:background-color")
 778            if bg_image is not None:
 779                properties.delete(bg_image)
 780            return
 781        # Add the properties if necessary
 782        if properties is None:
 783            properties = Element.from_tag(f"style:{family}-properties")
 784            self.append(properties)
 785        # Add the color...
 786        if color:
 787            properties.set_attribute("fo:background-color", color)
 788            if bg_image is not None:
 789                properties.delete(bg_image)
 790        # ... or the background
 791        elif url:
 792            properties.set_attribute("fo:background-color", "transparent")
 793            if bg_image is None:
 794                bg_image = Element.from_tag("style:background-image")  # type:ignore
 795                properties.append(bg_image)  # type:ignore
 796            bg_image.url = url  # type:ignore
 797            if position:
 798                bg_image.position = position  # type:ignore
 799            if repeat:
 800                bg_image.repeat = repeat  # type:ignore
 801            if opacity:
 802                bg_image.opacity = opacity  # type:ignore
 803            if filter:
 804                bg_image.filter = filter  # type:ignore
 805
 806    # list-style only:
 807
 808    def get_level_style(self, level: int) -> Style | None:
 809        if self.family != "list":
 810            return None
 811        level_styles = (
 812            "(text:list-level-style-number"
 813            "|text:list-level-style-bullet"
 814            "|text:list-level-style-image)"
 815        )
 816        return self._filtered_element(level_styles, 0, level=level)  # type: ignore
 817
 818    def set_level_style(  # noqa: C901
 819        self,
 820        level: int,
 821        num_format: str | None = None,
 822        bullet_char: str | None = None,
 823        url: str | None = None,
 824        display_levels: int | None = None,
 825        prefix: str | None = None,
 826        suffix: str | None = None,
 827        start_value: int | None = None,
 828        style: str | None = None,
 829        clone: Style | None = None,
 830    ) -> Style | None:
 831        """
 832        Arguments:
 833
 834            level -- int
 835
 836            num_format (for number) -- int
 837
 838            bullet_char (for bullet) -- str
 839
 840            url (for image) -- str
 841
 842            display_levels -- int
 843
 844            prefix -- str
 845
 846            suffix -- str
 847
 848            start_value -- int
 849
 850            style -- str
 851
 852            clone -- List Style
 853
 854        Return:
 855            level_style created
 856        """
 857        if self.family != "list":
 858            return None
 859        # Expected name
 860        if num_format is not None:
 861            level_style_name = "text:list-level-style-number"
 862        elif bullet_char is not None:
 863            level_style_name = "text:list-level-style-bullet"
 864        elif url is not None:
 865            level_style_name = "text:list-level-style-image"
 866        elif clone is not None:
 867            level_style_name = clone.tag
 868        else:
 869            raise ValueError("unknown level style type")
 870        was_created = False
 871        # Cloning or reusing an existing element
 872        level_style: Style | None = None
 873        if clone is not None:
 874            level_style = clone.clone  # type: ignore
 875            was_created = True
 876        else:
 877            level_style = self.get_level_style(level)
 878            if level_style is None:
 879                level_style = Element.from_tag(level_style_name)  # type: ignore
 880                was_created = True
 881        if level_style is None:
 882            return None
 883        # Transmute if the type changed
 884        if level_style.tag != level_style_name:
 885            print("Warn: different style", level_style_name, level_style.tag)
 886            level_style.tag = level_style_name
 887        # Set the level
 888        level_style.set_attribute("text:level", str(level))
 889        # Set the main attribute
 890        if num_format is not None:
 891            level_style.set_attribute("fo:num-format", num_format)
 892        elif bullet_char is not None:
 893            level_style.set_attribute("text:bullet-char", bullet_char)
 894        elif url is not None:
 895            level_style.set_attribute("xlink:href", url)
 896        # Set attributes
 897        if prefix:
 898            level_style.set_attribute("style:num-prefix", prefix)
 899        if suffix:
 900            level_style.set_attribute("style:num-suffix", suffix)
 901        if display_levels:
 902            level_style.set_attribute("text:display-levels", str(display_levels))
 903        if start_value:
 904            level_style.set_attribute("text:start-value", str(start_value))
 905        if style:
 906            level_style.text_style = style  # type: ignore
 907        # Commit the creation
 908        if was_created:
 909            self.append(level_style)
 910        return level_style
 911
 912    # page-layout only:
 913
 914    def get_header_style(self) -> Element | None:
 915        if self.family != "page-layout":
 916            return None
 917        return self.get_element("style:header-style")
 918
 919    def set_header_style(self, new_style: Style) -> None:
 920        if self.family != "page-layout":
 921            return
 922        header_style = self.get_header_style()
 923        if header_style is not None:
 924            self.delete(header_style)
 925        self.append(new_style)
 926
 927    def get_footer_style(self) -> Style | None:
 928        if self.family != "page-layout":
 929            return None
 930        return self.get_element("style:footer-style")  # type: ignore
 931
 932    def set_footer_style(self, new_style: Style) -> None:
 933        if self.family != "page-layout":
 934            return
 935        footer_style = self.get_footer_style()
 936        if footer_style is not None:
 937            self.delete(footer_style)
 938        self.append(new_style)
 939
 940    # master-page only:
 941
 942    def _set_header_or_footer(
 943        self,
 944        text_or_element: str | Element | list[Element | str],
 945        name: str = "header",
 946        style: str = "Header",
 947    ) -> None:
 948        if name == "header":
 949            header_or_footer = self.get_page_header()
 950        else:
 951            header_or_footer = self.get_page_footer()
 952        if header_or_footer is None:
 953            header_or_footer = Element.from_tag("style:" + name)
 954            self.append(header_or_footer)
 955        else:
 956            header_or_footer.clear()
 957        if (
 958            isinstance(text_or_element, Element)
 959            and text_or_element.tag == f"style:{name}"
 960        ):
 961            # Already a header or footer?
 962            self.delete(header_or_footer)
 963            self.append(text_or_element)
 964            return
 965        if isinstance(text_or_element, (Element, str)):
 966            elem_list: list[Element | str] = [text_or_element]
 967        else:
 968            elem_list = text_or_element
 969        for item in elem_list:
 970            if isinstance(item, str):
 971                paragraph = Element.from_tag("text:p")
 972                paragraph.style = style  # type: ignore
 973                header_or_footer.append(paragraph)
 974            elif isinstance(item, Element):
 975                header_or_footer.append(item)
 976
 977    def get_page_header(self) -> Element | None:
 978        """Get the element that contains the header contents.
 979
 980        If None, no header was set.
 981        """
 982        if self.family != "master-page":
 983            return None
 984        return self.get_element("style:header")
 985
 986    def set_page_header(
 987        self,
 988        text_or_element: str | Element | list[Element | str],
 989    ) -> None:
 990        """Create or replace the header by the given content. It can already
 991        be a complete header.
 992
 993        If you only want to update the existing header, get it and use the
 994        API.
 995
 996        Arguments:
 997
 998            text_or_element -- str or Element or a list of them
 999        """
1000        if self.family != "master-page":
1001            return None
1002        self._set_header_or_footer(text_or_element)
1003
1004    def get_page_footer(self) -> Element | None:
1005        """Get the element that contains the footer contents.
1006
1007        If None, no footer was set.
1008        """
1009        if self.family != "master-page":
1010            return None
1011        return self.get_element("style:footer")
1012
1013    def set_page_footer(
1014        self,
1015        text_or_element: str | Element | list[Element | str],
1016    ) -> None:
1017        """Create or replace the footer by the given content. It can already
1018        be a complete footer.
1019
1020        If you only want to update the existing footer, get it and use the
1021        API.
1022
1023        Arguments:
1024
1025            text_or_element -- str or Element or a list of them
1026        """
1027        if self.family != "master-page":
1028            return None
1029        self._set_header_or_footer(text_or_element, name="footer", style="Footer")
1030
1031    # font-face only:
1032
1033    def set_font(
1034        self,
1035        name: str,
1036        family: str | None = None,
1037        family_generic: str | None = None,
1038        pitch: str = "variable",
1039    ) -> None:
1040        if self.family != "font-face":
1041            return
1042        self.name = name
1043        if family is None:
1044            family = name
1045        self.svg_font_family = f'"{family}"'
1046        if family_generic is not None:
1047            self.font_family_generic = family_generic
1048        self.font_pitch = pitch

Style class for all these tags:

'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...

Style( family: str | None = None, name: str | None = None, display_name: str | None = None, parent_style: str | None = None, area: str | None = None, color: str | tuple | None = None, background_color: str | tuple | None = None, italic: bool = False, bold: bool = False, master_page: str | None = None, page_layout: str | None = None, next_style: str | None = None, data_style: str | None = None, border: str | None = None, border_top: str | None = None, border_right: str | None = None, border_bottom: str | None = None, border_left: str | None = None, padding: str | None = None, padding_top: str | None = None, padding_bottom: str | None = None, padding_left: str | None = None, padding_right: str | None = None, shadow: str | None = None, height: str | None = None, use_optimal_height: bool = False, width: str | None = None, break_before: str | None = None, break_after: str | None = None, min_height: str | None = None, font_name: str | None = None, font_family: str | None = None, font_family_generic: str | None = None, font_pitch: str = 'variable', **kwargs: Any)
379    def __init__(  # noqa: C901
380        self,
381        family: str | None = None,
382        name: str | None = None,
383        display_name: str | None = None,
384        parent_style: str | None = None,
385        # Where properties apply
386        area: str | None = None,
387        # For family 'text':
388        color: str | tuple | None = None,
389        background_color: str | tuple | None = None,
390        italic: bool = False,
391        bold: bool = False,
392        # For family 'paragraph'
393        master_page: str | None = None,
394        # For family 'master-page'
395        page_layout: str | None = None,
396        next_style: str | None = None,
397        # For family 'table-cell'
398        data_style: str | None = None,  # unused
399        border: str | None = None,
400        border_top: str | None = None,
401        border_right: str | None = None,
402        border_bottom: str | None = None,
403        border_left: str | None = None,
404        padding: str | None = None,
405        padding_top: str | None = None,
406        padding_bottom: str | None = None,
407        padding_left: str | None = None,
408        padding_right: str | None = None,
409        shadow: str | None = None,
410        # For family 'table-row'
411        height: str | None = None,
412        use_optimal_height: bool = False,
413        # For family 'table-column'
414        width: str | None = None,
415        break_before: str | None = None,
416        break_after: str | None = None,
417        # For family 'graphic'
418        min_height: str | None = None,
419        # For family 'font-face'
420        font_name: str | None = None,
421        font_family: str | None = None,
422        font_family_generic: str | None = None,
423        font_pitch: str = "variable",
424        # Every other property
425        **kwargs: Any,
426    ) -> None:
427        """Create a style of the given family. The name is not mandatory at this
428        point but will become required when inserting in a document as a common
429        style.
430
431        The display name is the name the user sees in an office application.
432
433        The parent_style is the name of the style this style will inherit from.
434
435        To set properties, pass them as keyword arguments. The area properties
436        apply to is optional and defaults to the family.
437
438        Arguments:
439
440            family -- 'paragraph', 'text', 'section', 'table', 'table-column',
441                      'table-row', 'table-cell', 'table-page', 'chart',
442                      'drawing-page', 'graphic', 'presentation',
443                      'control', 'ruby', 'list', 'number', 'page-layout'
444                      'font-face', or 'master-page'
445
446            name -- str
447
448            display_name -- str
449
450            parent_style -- str
451
452            area -- str
453
454        'text' Properties:
455
456            italic -- bool
457
458            bold -- bool
459
460        'paragraph' Properties:
461
462            master_page -- str
463
464        'master-page' Properties:
465
466            page_layout -- str
467
468            next_style -- str
469
470        'table-cell' Properties:
471
472            border, border_top, border_right, border_bottom, border_left -- str,
473            e.g. "0.002cm solid #000000" or 'none'
474
475            padding, padding_top, padding_right, padding_bottom, padding_left -- str,
476            e.g. "0.002cm" or 'none'
477
478            shadow -- str, e.g. "#808080 0.176cm 0.176cm"
479
480        'table-row' Properties:
481
482            height -- str, e.g. '5cm'
483
484            use_optimal_height -- bool
485
486        'table-column' Properties:
487
488            width -- str, e.g. '5cm'
489
490            break_before -- 'page', 'column' or 'auto'
491
492            break_after -- 'page', 'column' or 'auto'
493        """
494        self._family: str | None = None
495        tag_or_elem = kwargs.get("tag_or_elem", None)
496        if tag_or_elem is None:
497            family = to_str(family)
498            if family not in FAMILY_MAPPING:
499                raise ValueError("Unknown family value: %s" % family)
500            kwargs["tag"] = FAMILY_MAPPING[family]
501        super().__init__(**kwargs)
502        if self._do_init and family not in SUBCLASSED_STYLES:
503            kwargs.pop("tag", None)
504            kwargs.pop("tag_or_elem", None)
505            self.family = family  # relevant test made by property
506            # Common attributes
507            if name:
508                self.name = name
509            if display_name:
510                self.display_name = display_name
511            if parent_style:
512                self.parent_style = parent_style
513            # Paragraph
514            if family == "paragraph":
515                if master_page:
516                    self.master_page = master_page
517            # Master Page
518            elif family == "master-page":
519                if page_layout:
520                    self.page_layout = page_layout
521                if next_style:
522                    self.next_style = next_style
523            # Font face
524            elif family == "font-face":
525                if not font_name:
526                    raise ValueError("A font_name is required for 'font-face' style")
527                self.set_font(
528                    font_name,
529                    family=font_family,
530                    family_generic=font_family_generic,
531                    pitch=font_pitch,
532                )
533            # Properties
534            if area is None:
535                area = family
536            area = to_str(area)
537            # Text
538            if area == "text":
539                if color:
540                    kwargs["fo:color"] = color
541                if background_color:
542                    kwargs["fo:background-color"] = background_color
543                if italic:
544                    kwargs["fo:font-style"] = "italic"
545                    kwargs["style:font-style-asian"] = "italic"
546                    kwargs["style:font-style-complex"] = "italic"
547                if bold:
548                    kwargs["fo:font-weight"] = "bold"
549                    kwargs["style:font-weight-asian"] = "bold"
550                    kwargs["style:font-weight-complex"] = "bold"
551            # Table cell
552            elif area == "table-cell":
553                if border:
554                    kwargs["fo:border"] = border
555                elif border_top or border_right or border_bottom or border_left:
556                    kwargs["fo:border-top"] = border_top or "none"
557                    kwargs["fo:border-right"] = border_right or "none"
558                    kwargs["fo:border-bottom"] = border_bottom or "none"
559                    kwargs["fo:border-left"] = border_left or "none"
560                else:  # no border_top, ... neither border are defined
561                    pass  # left untouched
562                if padding:
563                    kwargs["fo:padding"] = padding
564                elif padding_top or padding_right or padding_bottom or padding_left:
565                    kwargs["fo:padding-top"] = padding_top or "none"
566                    kwargs["fo:padding-right"] = padding_right or "none"
567                    kwargs["fo:padding-bottom"] = padding_bottom or "none"
568                    kwargs["fo:padding-left"] = padding_left or "none"
569                else:  # no border_top, ... neither border are defined
570                    pass  # left untouched
571                if shadow:
572                    kwargs["style:shadow"] = shadow
573                if background_color:
574                    kwargs["fo:background-color"] = background_color
575            # Table row
576            elif area == "table-row":
577                if height:
578                    kwargs["style:row-height"] = height
579                if use_optimal_height:
580                    kwargs["style:use-optimal-row-height"] = Boolean.encode(
581                        use_optimal_height
582                    )
583                if background_color:
584                    kwargs["fo:background-color"] = background_color
585            # Table column
586            elif area == "table-column":
587                if width:
588                    kwargs["style:column-width"] = width
589                if break_before:
590                    kwargs["fo:break-before"] = break_before
591                if break_after:
592                    kwargs["fo:break-after"] = break_after
593            # Graphic
594            elif area == "graphic":
595                if min_height:
596                    kwargs["fo:min-height"] = min_height
597            # Every other properties
598            if kwargs:
599                self.set_properties(kwargs, area=area)

Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.

The display name is the name the user sees in an office application.

The parent_style is the name of the style this style will inherit from.

To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.

Arguments:

family -- 'paragraph', 'text', 'section', 'table', 'table-column',
          'table-row', 'table-cell', 'table-page', 'chart',
          'drawing-page', 'graphic', 'presentation',
          'control', 'ruby', 'list', 'number', 'page-layout'
          'font-face', or 'master-page'

name -- str

display_name -- str

parent_style -- str

area -- str

'text' Properties:

italic -- bool

bold -- bool

'paragraph' Properties:

master_page -- str

'master-page' Properties:

page_layout -- str

next_style -- str

'table-cell' Properties:

border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'

padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

'table-row' Properties:

height -- str, e.g. '5cm'

use_optimal_height -- bool

'table-column' Properties:

width -- str, e.g. '5cm'

break_before -- 'page', 'column' or 'auto'

break_after -- 'page', 'column' or 'auto'
family: str | None
601    @property
602    def family(self) -> str | None:
603        if self._family is None:
604            self._family = FALSE_FAMILY_MAP_REVERSE.get(
605                self.tag, self.get_attribute_string("style:family")
606            )
607        return self._family
def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
615    def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
616        """Get the mapping of all properties of this style. By default the
617        properties of the same family, e.g. a paragraph style and its
618        paragraph properties. Specify the area to get the text properties of
619        a paragraph style for example.
620
621        Arguments:
622
623            area -- str
624
625        Return: dict
626        """
627        if area is None:
628            area = self.family
629        element = self.get_element(f"style:{area}-properties")
630        if element is None:
631            return None
632        properties: dict[str, str | dict] = element.attributes  # type: ignore
633        # Nested properties are nested dictionaries
634        for child in element.children:
635            properties[child.tag] = child.attributes
636        return properties

Get the mapping of all properties of this style. By default the properties of the same family, e.g. a paragraph style and its paragraph properties. Specify the area to get the text properties of a paragraph style for example.

Arguments:

area -- str

Return: dict

def set_properties( self, properties: dict[str, str | dict] | None = None, style: Style | None = None, area: str | None = None, **kwargs: Any) -> None:
638    def set_properties(  # noqa: C901
639        self,
640        properties: dict[str, str | dict] | None = None,
641        style: Style | None = None,
642        area: str | None = None,
643        **kwargs: Any,
644    ) -> None:
645        """Set the properties of the "area" type of this style. Properties
646        are given either as a dict or as named arguments (or both). The area
647        is identical to the style family by default. If the properties
648        element is missing, it is created.
649
650        Instead of properties, you can pass a style with properties of the
651        same area. These will be copied.
652
653        Arguments:
654
655            properties -- dict
656
657            style -- Style
658
659            area -- 'paragraph', 'text'...
660        """
661        if properties is None:
662            properties = {}
663        if area is None:
664            if isinstance(self.family, bool):
665                area = None
666            else:
667                area = self.family
668        element = self.get_element(f"style:{area}-properties")
669        if element is None:
670            element = Element.from_tag(f"style:{area}-properties")
671            self.append(element)
672        if properties or kwargs:
673            properties = _expand_properties_dict(_merge_dicts(properties, kwargs))
674        elif style is not None:
675            properties = style.get_properties(area=area)
676            if properties is None:
677                return
678        if properties is None:
679            return
680        for key, value in properties.items():
681            if value is None:
682                element.del_attribute(key)
683            elif isinstance(value, (str, bool)):
684                element.set_attribute(key, value)
685            else:
686                pass

Set the properties of the "area" type of this style. Properties are given either as a dict or as named arguments (or both). The area is identical to the style family by default. If the properties element is missing, it is created.

Instead of properties, you can pass a style with properties of the same area. These will be copied.

Arguments:

properties -- dict

style -- Style

area -- 'paragraph', 'text'...
def del_properties( self, properties: list[str] | None = None, area: str | None = None) -> None:
688    def del_properties(
689        self,
690        properties: list[str] | None = None,
691        area: str | None = None,
692    ) -> None:
693        """Delete the given properties, either by list argument or
694        positional argument (or both). Remove only from the given area,
695        identical to the style family by default.
696
697        Arguments:
698
699            properties -- list
700
701            area -- str
702        """
703        if properties is None:
704            properties = []
705        if area is None:
706            area = self.family
707        element = self.get_element(f"style:{area}-properties")
708        if element is None:
709            raise ValueError(
710                f"properties element is inexistent for: style:{area}-properties"
711            )
712        for key in _expand_properties_list(properties):
713            element.del_attribute(key)

Delete the given properties, either by list argument or positional argument (or both). Remove only from the given area, identical to the style family by default.

Arguments:

properties -- list

area -- str
def set_background( self, color: str | None = None, url: str | None = None, position: str | None = 'center', repeat: str | None = None, opacity: str | None = None, filter: str | None = None) -> None:
715    def set_background(  # noqa: C901
716        self,
717        color: str | None = None,
718        url: str | None = None,
719        position: str | None = "center",
720        repeat: str | None = None,
721        opacity: str | None = None,
722        filter: str | None = None,  # noqa: A002
723    ) -> None:
724        """Set the background color of a text style, or the background color
725        or image of a paragraph style or page layout.
726
727        With no argument, remove any existing background.
728
729        The position is one or two of 'center', 'left', 'right', 'top' or
730        'bottom'.
731
732        The repeat is 'no-repeat', 'repeat' or 'stretch'.
733
734        The opacity is a percentage integer (not a string with the '%s' sign)
735
736        The filter is an application-specific filter name defined elsewhere.
737
738        Though this method is defined on the base style class, it will raise
739        an error if the style type is not compatible.
740
741        Arguments:
742
743            color -- '#rrggbb'
744
745            url -- str
746
747            position -- str
748
749            repeat -- str
750
751            opacity -- int
752
753            filter -- str
754        """
755        family = self.family
756        if family not in {
757            "text",
758            "paragraph",
759            "page-layout",
760            "section",
761            "table",
762            "table-row",
763            "table-cell",
764            "graphic",
765        }:
766            raise TypeError("No background support for this family")
767        if url is not None and family == "text":
768            raise TypeError("No background image for text styles")
769        properties = self.get_element(f"style:{family}-properties")
770        bg_image: BackgroundImage | None = None
771        if properties is not None:
772            bg_image = properties.get_element("style:background-image")  # type:ignore
773        # Erasing
774        if color is None and url is None:
775            if properties is None:
776                return
777            properties.del_attribute("fo:background-color")
778            if bg_image is not None:
779                properties.delete(bg_image)
780            return
781        # Add the properties if necessary
782        if properties is None:
783            properties = Element.from_tag(f"style:{family}-properties")
784            self.append(properties)
785        # Add the color...
786        if color:
787            properties.set_attribute("fo:background-color", color)
788            if bg_image is not None:
789                properties.delete(bg_image)
790        # ... or the background
791        elif url:
792            properties.set_attribute("fo:background-color", "transparent")
793            if bg_image is None:
794                bg_image = Element.from_tag("style:background-image")  # type:ignore
795                properties.append(bg_image)  # type:ignore
796            bg_image.url = url  # type:ignore
797            if position:
798                bg_image.position = position  # type:ignore
799            if repeat:
800                bg_image.repeat = repeat  # type:ignore
801            if opacity:
802                bg_image.opacity = opacity  # type:ignore
803            if filter:
804                bg_image.filter = filter  # type:ignore

Set the background color of a text style, or the background color or image of a paragraph style or page layout.

With no argument, remove any existing background.

The position is one or two of 'center', 'left', 'right', 'top' or 'bottom'.

The repeat is 'no-repeat', 'repeat' or 'stretch'.

The opacity is a percentage integer (not a string with the '%s' sign)

The filter is an application-specific filter name defined elsewhere.

Though this method is defined on the base style class, it will raise an error if the style type is not compatible.

Arguments:

color -- '#rrggbb'

url -- str

position -- str

repeat -- str

opacity -- int

filter -- str
def get_level_style(self, level: int) -> Style | None:
808    def get_level_style(self, level: int) -> Style | None:
809        if self.family != "list":
810            return None
811        level_styles = (
812            "(text:list-level-style-number"
813            "|text:list-level-style-bullet"
814            "|text:list-level-style-image)"
815        )
816        return self._filtered_element(level_styles, 0, level=level)  # type: ignore
def set_level_style( self, level: int, num_format: str | None = None, bullet_char: str | None = None, url: str | None = None, display_levels: int | None = None, prefix: str | None = None, suffix: str | None = None, start_value: int | None = None, style: str | None = None, clone: Style | None = None) -> Style | None:
818    def set_level_style(  # noqa: C901
819        self,
820        level: int,
821        num_format: str | None = None,
822        bullet_char: str | None = None,
823        url: str | None = None,
824        display_levels: int | None = None,
825        prefix: str | None = None,
826        suffix: str | None = None,
827        start_value: int | None = None,
828        style: str | None = None,
829        clone: Style | None = None,
830    ) -> Style | None:
831        """
832        Arguments:
833
834            level -- int
835
836            num_format (for number) -- int
837
838            bullet_char (for bullet) -- str
839
840            url (for image) -- str
841
842            display_levels -- int
843
844            prefix -- str
845
846            suffix -- str
847
848            start_value -- int
849
850            style -- str
851
852            clone -- List Style
853
854        Return:
855            level_style created
856        """
857        if self.family != "list":
858            return None
859        # Expected name
860        if num_format is not None:
861            level_style_name = "text:list-level-style-number"
862        elif bullet_char is not None:
863            level_style_name = "text:list-level-style-bullet"
864        elif url is not None:
865            level_style_name = "text:list-level-style-image"
866        elif clone is not None:
867            level_style_name = clone.tag
868        else:
869            raise ValueError("unknown level style type")
870        was_created = False
871        # Cloning or reusing an existing element
872        level_style: Style | None = None
873        if clone is not None:
874            level_style = clone.clone  # type: ignore
875            was_created = True
876        else:
877            level_style = self.get_level_style(level)
878            if level_style is None:
879                level_style = Element.from_tag(level_style_name)  # type: ignore
880                was_created = True
881        if level_style is None:
882            return None
883        # Transmute if the type changed
884        if level_style.tag != level_style_name:
885            print("Warn: different style", level_style_name, level_style.tag)
886            level_style.tag = level_style_name
887        # Set the level
888        level_style.set_attribute("text:level", str(level))
889        # Set the main attribute
890        if num_format is not None:
891            level_style.set_attribute("fo:num-format", num_format)
892        elif bullet_char is not None:
893            level_style.set_attribute("text:bullet-char", bullet_char)
894        elif url is not None:
895            level_style.set_attribute("xlink:href", url)
896        # Set attributes
897        if prefix:
898            level_style.set_attribute("style:num-prefix", prefix)
899        if suffix:
900            level_style.set_attribute("style:num-suffix", suffix)
901        if display_levels:
902            level_style.set_attribute("text:display-levels", str(display_levels))
903        if start_value:
904            level_style.set_attribute("text:start-value", str(start_value))
905        if style:
906            level_style.text_style = style  # type: ignore
907        # Commit the creation
908        if was_created:
909            self.append(level_style)
910        return level_style

Arguments:

level -- int

num_format (for number) -- int

bullet_char (for bullet) -- str

url (for image) -- str

display_levels -- int

prefix -- str

suffix -- str

start_value -- int

style -- str

clone -- List Style

Return: level_style created

def get_header_style(self) -> Element | None:
914    def get_header_style(self) -> Element | None:
915        if self.family != "page-layout":
916            return None
917        return self.get_element("style:header-style")
def set_header_style(self, new_style: Style) -> None:
919    def set_header_style(self, new_style: Style) -> None:
920        if self.family != "page-layout":
921            return
922        header_style = self.get_header_style()
923        if header_style is not None:
924            self.delete(header_style)
925        self.append(new_style)
def get_page_header(self) -> Element | None:
977    def get_page_header(self) -> Element | None:
978        """Get the element that contains the header contents.
979
980        If None, no header was set.
981        """
982        if self.family != "master-page":
983            return None
984        return self.get_element("style:header")

Get the element that contains the header contents.

If None, no header was set.

def set_page_header( self, text_or_element: str | Element | list[Element | str]) -> None:
 986    def set_page_header(
 987        self,
 988        text_or_element: str | Element | list[Element | str],
 989    ) -> None:
 990        """Create or replace the header by the given content. It can already
 991        be a complete header.
 992
 993        If you only want to update the existing header, get it and use the
 994        API.
 995
 996        Arguments:
 997
 998            text_or_element -- str or Element or a list of them
 999        """
1000        if self.family != "master-page":
1001            return None
1002        self._set_header_or_footer(text_or_element)

Create or replace the header by the given content. It can already be a complete header.

If you only want to update the existing header, get it and use the API.

Arguments:

text_or_element -- str or Element or a list of them
def set_font( self, name: str, family: str | None = None, family_generic: str | None = None, pitch: str = 'variable') -> None:
1033    def set_font(
1034        self,
1035        name: str,
1036        family: str | None = None,
1037        family_generic: str | None = None,
1038        pitch: str = "variable",
1039    ) -> None:
1040        if self.family != "font-face":
1041            return
1042        self.name = name
1043        if family is None:
1044            family = name
1045        self.svg_font_family = f'"{family}"'
1046        if family_generic is not None:
1047            self.font_family_generic = family_generic
1048        self.font_pitch = pitch
page_layout: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
next_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
parent_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
display_name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
svg_font_family: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
font_family_generic: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
font_pitch: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
text_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
master_page: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style_type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_text: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style_position: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Styles(odfdo.XmlPart):
 58class Styles(XmlPart):
 59    def _get_style_contexts(
 60        self, family: str, automatic: bool = False
 61    ) -> list[Element]:
 62        if automatic:
 63            return [self.get_element("//office:automatic-styles")]
 64        if not family:
 65            # All possibilities
 66            return [
 67                self.get_element("//office:automatic-styles"),
 68                self.get_element("//office:styles"),
 69                self.get_element("//office:master-styles"),
 70                self.get_element("//office:font-face-decls"),
 71            ]
 72        queries = CONTEXT_MAPPING.get(family)
 73        if queries is None:
 74            raise ValueError(f"unknown family: {family}")
 75        # print('q:', queries)
 76        return [self.get_element(query) for query in queries]
 77
 78    def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]:
 79        """Return the list of styles in the Content part, optionally limited
 80        to the given family, optionaly limited to automatic styles.
 81
 82        Arguments:
 83
 84            family -- str
 85
 86            automatic -- bool
 87
 88        Return: list of Style
 89        """
 90        result = []
 91        for context in self._get_style_contexts(family, automatic=automatic):
 92            if context is None:
 93                continue
 94            # print('-ctx----', automatic)
 95            # print(context.tag)
 96            # print(context.__class__)
 97            # print(context.serialize())
 98            result.extend(context.get_styles(family=family))
 99        return result
100
101    def get_style(
102        self,
103        family: str,
104        name_or_element: str | Style | None = None,
105        display_name: str | None = None,
106    ) -> Style | None:
107        """Return the style uniquely identified by the name/family pair. If
108        the argument is already a style object, it will return it.
109
110        If the name is None, the default style is fetched.
111
112        If the name is not the internal name but the name you gave in the
113        desktop application, use display_name instead.
114
115        Arguments:
116
117            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
118                      'number', 'page-layout', 'master-page'
119
120            name_or_element -- str, odf_style or None
121
122            display_name -- str or None
123
124        Return: odf_style or None if not found
125        """
126        for context in self._get_style_contexts(family):
127            if context is None:
128                continue
129            style = context.get_style(
130                family,
131                name_or_element=name_or_element,
132                display_name=display_name,
133            )
134            if style is not None:
135                return style  # type: ignore
136        return None
137
138    def get_master_pages(self) -> list[Element]:
139        query = make_xpath_query("descendant::style:master-page")
140        return self.get_elements(query)  # type:ignore
141
142    def get_master_page(self, position: int = 0) -> Element | None:
143        results = self.get_master_pages()
144        try:
145            return results[position]
146        except IndexError:
147            return None

Representation of an XML part.

Abstraction of the XML library behind.

def get_styles( self, family: str = '', automatic: bool = False) -> list[Element]:
78    def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]:
79        """Return the list of styles in the Content part, optionally limited
80        to the given family, optionaly limited to automatic styles.
81
82        Arguments:
83
84            family -- str
85
86            automatic -- bool
87
88        Return: list of Style
89        """
90        result = []
91        for context in self._get_style_contexts(family, automatic=automatic):
92            if context is None:
93                continue
94            # print('-ctx----', automatic)
95            # print(context.tag)
96            # print(context.__class__)
97            # print(context.serialize())
98            result.extend(context.get_styles(family=family))
99        return result

Return the list of styles in the Content part, optionally limited to the given family, optionaly limited to automatic styles.

Arguments:

family -- str

automatic -- bool

Return: list of Style

def get_style( self, family: str, name_or_element: str | Style | None = None, display_name: str | None = None) -> Style | None:
101    def get_style(
102        self,
103        family: str,
104        name_or_element: str | Style | None = None,
105        display_name: str | None = None,
106    ) -> Style | None:
107        """Return the style uniquely identified by the name/family pair. If
108        the argument is already a style object, it will return it.
109
110        If the name is None, the default style is fetched.
111
112        If the name is not the internal name but the name you gave in the
113        desktop application, use display_name instead.
114
115        Arguments:
116
117            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
118                      'number', 'page-layout', 'master-page'
119
120            name_or_element -- str, odf_style or None
121
122            display_name -- str or None
123
124        Return: odf_style or None if not found
125        """
126        for context in self._get_style_contexts(family):
127            if context is None:
128                continue
129            style = context.get_style(
130                family,
131                name_or_element=name_or_element,
132                display_name=display_name,
133            )
134            if style is not None:
135                return style  # type: ignore
136        return None

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text',  'graphic', 'table', 'list',
          'number', 'page-layout', 'master-page'

name_or_element -- str, odf_style or None

display_name -- str or None

Return: odf_style or None if not found

def get_master_pages(self) -> list[Element]:
138    def get_master_pages(self) -> list[Element]:
139        query = make_xpath_query("descendant::style:master-page")
140        return self.get_elements(query)  # type:ignore
def get_master_page(self, position: int = 0) -> Element | None:
142    def get_master_page(self, position: int = 0) -> Element | None:
143        results = self.get_master_pages()
144        try:
145            return results[position]
146        except IndexError:
147            return None
class TOC(odfdo.Element):
168class TOC(Element):
169    """Table of content.
170    The "text:table-of-content" element represents a table of contents for a
171    document. The items that can be listed in a table of contents are:
172      - Headings (as defined by the outline structure of the document), up to
173        a selected level.
174      - Table of contents index marks.
175      - Paragraphs formatted with specified paragraph styles.
176
177
178    Implementation:
179    Default parameters are what most people use: protected from manual
180    modifications and not limited in title levels.
181
182    The name is mandatory and derived automatically from the title if not
183    given. Provide one in case of a conflict with other TOCs in the same
184    document.
185
186    The "text:table-of-content" element has the following attributes:
187    text:name, text:protected, text:protection-key,
188    text:protection-key-digest-algorithm, text:style-name and xml:id.
189
190    Arguments:
191
192        title -- str
193
194        name -- str
195
196        protected -- bool
197
198        outline_level -- int
199
200        style -- str
201
202        title_style -- str
203
204        entry_style -- str
205    """
206
207    _tag = "text:table-of-content"
208    _properties = (
209        PropDef("name", "text:name"),
210        PropDef("style", "text:style-name"),
211        PropDef("xml_id", "xml:id"),
212        PropDef("protected", "text:protected"),
213        PropDef("protection_key", "text:protection-key"),
214        PropDef(
215            "protection_key_digest_algorithm", "text:protection-key-digest-algorithm"
216        ),
217    )
218
219    def __init__(
220        self,
221        title: str = "Table of Contents",
222        name: str | None = None,
223        protected: bool = True,
224        outline_level: int = 0,
225        style: str | None = None,
226        title_style: str = "Contents_20_Heading",
227        entry_style: str = "Contents_20_%d",
228        **kwargs: Any,
229    ) -> None:
230        super().__init__(**kwargs)
231        if self._do_init:
232            if style:
233                self.style = style
234            if protected:
235                self.protected = protected
236            if name is None:
237                self.name = f"{title}1"
238            # Create the source template
239            toc_source = self.create_toc_source(
240                title, outline_level, title_style, entry_style
241            )
242            self.append(toc_source)
243            # Create the index body automatically with the index title
244            if title:
245                # This style is in the template document
246                self.set_toc_title(title, text_style=title_style)
247
248    @staticmethod
249    def create_toc_source(
250        title: str,
251        outline_level: int,
252        title_style: str,
253        entry_style: str,
254    ) -> Element:
255        toc_source = Element.from_tag("text:table-of-content-source")
256        toc_source.set_attribute("text:outline-level", str(outline_level))
257        if title:
258            title_template = IndexTitleTemplate()
259            if title_style:
260                # This style is in the template document
261                title_template.style = title_style
262            title_template.text = title
263            toc_source.append(title_template)
264        for level in range(1, 11):
265            template = TocEntryTemplate(outline_level=level)
266            if entry_style:
267                template.style = entry_style % level
268            toc_source.append(template)
269        return toc_source
270
271    def __str__(self) -> str:
272        return self.get_formatted_text()
273
274    def get_formatted_text(self, context: dict | None = None) -> str:
275        index_body = self.get_element("text:index-body")
276
277        if index_body is None:
278            return ""
279        if context is None:
280            context = {}
281        if context.get("rst_mode"):
282            return "\n.. contents::\n\n"
283
284        result = []
285        for element in index_body.children:
286            if element.tag == "text:index-title":
287                for child_element in element.children:
288                    result.append(child_element.get_formatted_text(context).strip())
289            else:
290                result.append(element.get_formatted_text(context).strip())
291        return "\n".join(result)
292
293    @property
294    def outline_level(self) -> int | None:
295        source = self.get_element("text:table-of-content-source")
296        if source is None:
297            return None
298        return source.get_attribute_integer("text:outline-level")
299
300    @outline_level.setter
301    def outline_level(self, level: int) -> None:
302        source = self.get_element("text:table-of-content-source")
303        if source is None:
304            source = Element.from_tag("text:table-of-content-source")
305            self.insert(source, FIRST_CHILD)
306        source.set_attribute("text:outline-level", str(level))
307
308    @property
309    def body(self) -> Element | None:
310        return self.get_element("text:index-body")
311
312    @body.setter
313    def body(self, body: Element | None = None) -> Element | None:
314        old_body = self.body
315        if old_body is not None:
316            self.delete(old_body)
317        if body is None:
318            body = Element.from_tag("text:index-body")
319        self.append(body)
320        return body
321
322    def get_title(self) -> str:
323        index_body = self.body
324        if index_body is None:
325            return ""
326        index_title = index_body.get_element(IndexTitle._tag)
327        if index_title is None:
328            return ""
329        return index_title.text_content
330
331    def set_toc_title(
332        self,
333        title: str,
334        style: str | None = None,
335        text_style: str | None = None,
336    ) -> None:
337        index_body = self.body
338        if index_body is None:
339            self.body = None
340            index_body = self.body
341        index_title = index_body.get_element(IndexTitle._tag)  # type: ignore
342        if index_title is None:
343            name = f"{self.name}_Head"
344            index_title = IndexTitle(
345                name=name, style=style, title_text=title, text_style=text_style
346            )
347            index_body.append(index_title)  # type: ignore
348        else:
349            if style:
350                index_title.style = style  # type: ignore
351            paragraph = index_title.get_paragraph()
352            if paragraph is None:
353                paragraph = Paragraph()
354                index_title.append(paragraph)
355            if text_style:
356                paragraph.style = text_style  # type: ignore
357            paragraph.text = title
358
359    @staticmethod
360    def _header_numbering(level_indexes: dict[int, int], level: int) -> str:
361        """Return the header hierarchical number (like "1.2.3.")."""
362        numbers: list[int] = []
363        # before header level
364        for idx in range(1, level):
365            numbers.append(level_indexes.setdefault(idx, 1))
366        # header level
367        index = level_indexes.get(level, 0) + 1
368        level_indexes[level] = index
369        numbers.append(index)
370        # after header level
371        idx = level + 1
372        while idx in level_indexes:
373            del level_indexes[idx]
374            idx += 1
375        return ".".join(str(x) for x in numbers) + "."
376
377    def fill(  # noqa: C901
378        self,
379        document: Document | None = None,
380        use_default_styles: bool = True,
381    ) -> None:
382        """Fill the TOC with the titles found in the document. A TOC is not
383        contextual so it will catch all titles before and after its insertion.
384        If the TOC is not attached to a document, attach it beforehand or
385        provide one as argument.
386
387        For having a pretty TOC, let use_default_styles by default.
388
389        Arguments:
390
391            document -- Document
392
393            use_default_styles -- bool
394        """
395        # Find the body
396        if document is not None:
397            body: Element | None = document.body
398        else:
399            body = self.document_body
400        if body is None:
401            raise ValueError("The TOC must be related to a document somehow")
402
403        # Save the title
404        index_body = self.body
405        title = index_body.get_element("text:index-title")  # type: ignore
406
407        # Clean the old index-body
408        self.body = None
409        index_body = self.body
410
411        # Restore the title
412        if title and str(title):
413            index_body.insert(title, position=0)  # type: ignore
414
415        # Insert default TOC style
416        if use_default_styles:
417            automatic_styles = body.get_element("//office:automatic-styles")
418            if isinstance(automatic_styles, Element):
419                for level in range(1, 11):
420                    if (
421                        automatic_styles.get_style(
422                            "paragraph", _toc_entry_style_name(level)
423                        )
424                        is None
425                    ):
426                        level_style = default_toc_level_style(level)
427                        automatic_styles.append(level_style)
428
429        # Auto-fill the index
430        outline_level = self.outline_level or 10
431        level_indexes: dict[int, int] = {}
432        for header in body.get_headers():
433            level = header.get_attribute_integer("text:outline-level") or 0
434            if level is None or level > outline_level:
435                continue
436            number_str = self._header_numbering(level_indexes, level)
437            # Make the title with "1.2.3. Title" format
438            paragraph = Paragraph(f"{number_str} {header}")
439            if use_default_styles:
440                paragraph.style = _toc_entry_style_name(level)
441            index_body.append(paragraph)  # type: ignore

Table of content. The "text:table-of-content" element represents a table of contents for a document. The items that can be listed in a table of contents are:

  • Headings (as defined by the outline structure of the document), up to a selected level.
  • Table of contents index marks.
  • Paragraphs formatted with specified paragraph styles.

Implementation: Default parameters are what most people use: protected from manual modifications and not limited in title levels.

The name is mandatory and derived automatically from the title if not given. Provide one in case of a conflict with other TOCs in the same document.

The "text:table-of-content" element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name and xml:id.

Arguments:

title -- str

name -- str

protected -- bool

outline_level -- int

style -- str

title_style -- str

entry_style -- str
TOC( title: str = 'Table of Contents', name: str | None = None, protected: bool = True, outline_level: int = 0, style: str | None = None, title_style: str = 'Contents_20_Heading', entry_style: str = 'Contents_20_%d', **kwargs: Any)
219    def __init__(
220        self,
221        title: str = "Table of Contents",
222        name: str | None = None,
223        protected: bool = True,
224        outline_level: int = 0,
225        style: str | None = None,
226        title_style: str = "Contents_20_Heading",
227        entry_style: str = "Contents_20_%d",
228        **kwargs: Any,
229    ) -> None:
230        super().__init__(**kwargs)
231        if self._do_init:
232            if style:
233                self.style = style
234            if protected:
235                self.protected = protected
236            if name is None:
237                self.name = f"{title}1"
238            # Create the source template
239            toc_source = self.create_toc_source(
240                title, outline_level, title_style, entry_style
241            )
242            self.append(toc_source)
243            # Create the index body automatically with the index title
244            if title:
245                # This style is in the template document
246                self.set_toc_title(title, text_style=title_style)
@staticmethod
def create_toc_source( title: str, outline_level: int, title_style: str, entry_style: str) -> Element:
248    @staticmethod
249    def create_toc_source(
250        title: str,
251        outline_level: int,
252        title_style: str,
253        entry_style: str,
254    ) -> Element:
255        toc_source = Element.from_tag("text:table-of-content-source")
256        toc_source.set_attribute("text:outline-level", str(outline_level))
257        if title:
258            title_template = IndexTitleTemplate()
259            if title_style:
260                # This style is in the template document
261                title_template.style = title_style
262            title_template.text = title
263            toc_source.append(title_template)
264        for level in range(1, 11):
265            template = TocEntryTemplate(outline_level=level)
266            if entry_style:
267                template.style = entry_style % level
268            toc_source.append(template)
269        return toc_source
def get_formatted_text(self, context: dict | None = None) -> str:
274    def get_formatted_text(self, context: dict | None = None) -> str:
275        index_body = self.get_element("text:index-body")
276
277        if index_body is None:
278            return ""
279        if context is None:
280            context = {}
281        if context.get("rst_mode"):
282            return "\n.. contents::\n\n"
283
284        result = []
285        for element in index_body.children:
286            if element.tag == "text:index-title":
287                for child_element in element.children:
288                    result.append(child_element.get_formatted_text(context).strip())
289            else:
290                result.append(element.get_formatted_text(context).strip())
291        return "\n".join(result)

This function should return a beautiful version of the text.

outline_level: int | None
293    @property
294    def outline_level(self) -> int | None:
295        source = self.get_element("text:table-of-content-source")
296        if source is None:
297            return None
298        return source.get_attribute_integer("text:outline-level")
body: Element | None
308    @property
309    def body(self) -> Element | None:
310        return self.get_element("text:index-body")
def get_title(self) -> str:
322    def get_title(self) -> str:
323        index_body = self.body
324        if index_body is None:
325            return ""
326        index_title = index_body.get_element(IndexTitle._tag)
327        if index_title is None:
328            return ""
329        return index_title.text_content
def set_toc_title( self, title: str, style: str | None = None, text_style: str | None = None) -> None:
331    def set_toc_title(
332        self,
333        title: str,
334        style: str | None = None,
335        text_style: str | None = None,
336    ) -> None:
337        index_body = self.body
338        if index_body is None:
339            self.body = None
340            index_body = self.body
341        index_title = index_body.get_element(IndexTitle._tag)  # type: ignore
342        if index_title is None:
343            name = f"{self.name}_Head"
344            index_title = IndexTitle(
345                name=name, style=style, title_text=title, text_style=text_style
346            )
347            index_body.append(index_title)  # type: ignore
348        else:
349            if style:
350                index_title.style = style  # type: ignore
351            paragraph = index_title.get_paragraph()
352            if paragraph is None:
353                paragraph = Paragraph()
354                index_title.append(paragraph)
355            if text_style:
356                paragraph.style = text_style  # type: ignore
357            paragraph.text = title
def fill( self, document: Document | None = None, use_default_styles: bool = True) -> None:
377    def fill(  # noqa: C901
378        self,
379        document: Document | None = None,
380        use_default_styles: bool = True,
381    ) -> None:
382        """Fill the TOC with the titles found in the document. A TOC is not
383        contextual so it will catch all titles before and after its insertion.
384        If the TOC is not attached to a document, attach it beforehand or
385        provide one as argument.
386
387        For having a pretty TOC, let use_default_styles by default.
388
389        Arguments:
390
391            document -- Document
392
393            use_default_styles -- bool
394        """
395        # Find the body
396        if document is not None:
397            body: Element | None = document.body
398        else:
399            body = self.document_body
400        if body is None:
401            raise ValueError("The TOC must be related to a document somehow")
402
403        # Save the title
404        index_body = self.body
405        title = index_body.get_element("text:index-title")  # type: ignore
406
407        # Clean the old index-body
408        self.body = None
409        index_body = self.body
410
411        # Restore the title
412        if title and str(title):
413            index_body.insert(title, position=0)  # type: ignore
414
415        # Insert default TOC style
416        if use_default_styles:
417            automatic_styles = body.get_element("//office:automatic-styles")
418            if isinstance(automatic_styles, Element):
419                for level in range(1, 11):
420                    if (
421                        automatic_styles.get_style(
422                            "paragraph", _toc_entry_style_name(level)
423                        )
424                        is None
425                    ):
426                        level_style = default_toc_level_style(level)
427                        automatic_styles.append(level_style)
428
429        # Auto-fill the index
430        outline_level = self.outline_level or 10
431        level_indexes: dict[int, int] = {}
432        for header in body.get_headers():
433            level = header.get_attribute_integer("text:outline-level") or 0
434            if level is None or level > outline_level:
435                continue
436            number_str = self._header_numbering(level_indexes, level)
437            # Make the title with "1.2.3. Title" format
438            paragraph = Paragraph(f"{number_str} {header}")
439            if use_default_styles:
440                paragraph.style = _toc_entry_style_name(level)
441            index_body.append(paragraph)  # type: ignore

Fill the TOC with the titles found in the document. A TOC is not contextual so it will catch all titles before and after its insertion. If the TOC is not attached to a document, attach it beforehand or provide one as argument.

For having a pretty TOC, let use_default_styles by default.

Arguments:

document -- Document

use_default_styles -- bool
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
xml_id: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
protected: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
protection_key: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
protection_key_digest_algorithm: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Tab(odfdo.Element):
194class Tab(Element):
195    """This element represents the [UNICODE] tab character (HORIZONTAL
196    TABULATION, U+0009).
197
198    The position attribute contains the number of the tab-stop to which
199    a tab character refers. The position 0 marks the start margin of a
200    paragraph. Note: The position attribute is only a hint to help non-layout
201    oriented consumers to determine the tab/tab-stop association. Layout
202    oriented consumers should determine the tab positions based on the style
203    information
204    """
205
206    _tag = "text:tab"
207    _properties: tuple[PropDef, ...] = (PropDef("position", "text:tab-ref"),)
208
209    def __init__(self, position: int | None = None, **kwargs: Any) -> None:
210        """
211        Arguments:
212
213            position -- int
214        """
215        super().__init__(**kwargs)
216        if self._do_init and position is not None and position >= 0:
217            self.position = str(position)

This element represents the [UNICODE] tab character (HORIZONTAL TABULATION, U+0009).

The position attribute contains the number of the tab-stop to which a tab character refers. The position 0 marks the start margin of a paragraph. Note: The position attribute is only a hint to help non-layout oriented consumers to determine the tab/tab-stop association. Layout oriented consumers should determine the tab positions based on the style information

Tab(position: int | None = None, **kwargs: Any)
209    def __init__(self, position: int | None = None, **kwargs: Any) -> None:
210        """
211        Arguments:
212
213            position -- int
214        """
215        super().__init__(**kwargs)
216        if self._do_init and position is not None and position >= 0:
217            self.position = str(position)

Arguments:

position -- int
position: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TabStopStyle(odfdo.Element):
 94class TabStopStyle(Element):
 95    """ODF "style:tab-stop"
 96    Base style for a TOC entryBase style for a TOC entry
 97    """
 98
 99    _tag = "style:tab-stop"
100    _properties = (
101        PropDef("style_char", "style:char"),
102        PropDef("leader_color", "style:leader-color"),
103        PropDef("leader_style", "style:leader-style"),
104        PropDef("leader_text", "style:leader-text"),
105        PropDef("leader_text_style", "style:leader-text-style"),
106        PropDef("leader_type", "style:leader-type"),
107        PropDef("leader_width", "style:leader-width"),
108        PropDef("style_position", "style:position"),
109        PropDef("style_type", "style:type"),
110    )
111
112    def __init__(  # noqa: C901
113        self,
114        style_char: str | None = None,
115        leader_color: str | None = None,
116        leader_style: str | None = None,
117        leader_text: str | None = None,
118        leader_text_style: str | None = None,
119        leader_type: str | None = None,
120        leader_width: str | None = None,
121        style_position: str | None = None,
122        style_type: str | None = None,
123        **kwargs: Any,
124    ):
125        super().__init__(**kwargs)
126        if self._do_init:
127            if style_char:
128                self.style_char = style_char
129            if leader_color:
130                self.leader_color = leader_color
131            if leader_style:
132                self.leader_style = leader_style
133            if leader_text:
134                self.leader_text = leader_text
135            if leader_text_style:
136                self.leader_text_style = leader_text_style
137            if leader_type:
138                self.leader_type = leader_type
139            if leader_width:
140                self.leader_width = leader_width
141            if style_position:
142                self.style_position = style_position
143            if style_type:
144                self.style_type = style_type

ODF "style:tab-stop" Base style for a TOC entryBase style for a TOC entry

TabStopStyle( style_char: str | None = None, leader_color: str | None = None, leader_style: str | None = None, leader_text: str | None = None, leader_text_style: str | None = None, leader_type: str | None = None, leader_width: str | None = None, style_position: str | None = None, style_type: str | None = None, **kwargs: Any)
112    def __init__(  # noqa: C901
113        self,
114        style_char: str | None = None,
115        leader_color: str | None = None,
116        leader_style: str | None = None,
117        leader_text: str | None = None,
118        leader_text_style: str | None = None,
119        leader_type: str | None = None,
120        leader_width: str | None = None,
121        style_position: str | None = None,
122        style_type: str | None = None,
123        **kwargs: Any,
124    ):
125        super().__init__(**kwargs)
126        if self._do_init:
127            if style_char:
128                self.style_char = style_char
129            if leader_color:
130                self.leader_color = leader_color
131            if leader_style:
132                self.leader_style = leader_style
133            if leader_text:
134                self.leader_text = leader_text
135            if leader_text_style:
136                self.leader_text_style = leader_text_style
137            if leader_type:
138                self.leader_type = leader_type
139            if leader_width:
140                self.leader_width = leader_width
141            if style_position:
142                self.style_position = style_position
143            if style_type:
144                self.style_type = style_type
style_char: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_color: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_text: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_text_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
leader_width: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style_position: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style_type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Table(odfdo.Element):
 284class Table(Element):
 285    """ODF table "table:table" """
 286
 287    _tag = "table:table"
 288    _caching = True
 289    _append = Element.append
 290
 291    def __init__(
 292        self,
 293        name: str | None = None,
 294        width: int | None = None,
 295        height: int | None = None,
 296        protected: bool = False,
 297        protection_key: str | None = None,
 298        display: bool = True,
 299        printable: bool = True,
 300        print_ranges: list[str] | None = None,
 301        style: str | None = None,
 302        **kwargs: Any,
 303    ) -> None:
 304        """Create a table element, optionally prefilled with "height" rows of
 305        "width" cells each.
 306
 307        If the table is to be protected, a protection key must be provided,
 308        i.e. a hash value of the password.
 309
 310        If the table must not be displayed, set "display" to False.
 311
 312        If the table must not be printed, set "printable" to False. The table
 313        will not be printed when it is not displayed, whatever the value of
 314        this argument.
 315
 316        Ranges of cells to print can be provided as a list of cell ranges,
 317        e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g.
 318        "E6:K12 P6:R12".
 319
 320        You can access and modify the XML tree manually, but you probably want
 321        to use the API to access and alter cells. It will save you from
 322        handling repetitions and the same number of cells for each row.
 323
 324        If you use both the table API and the XML API, you are on your own for
 325        ensuiring model integrity.
 326
 327        Arguments:
 328
 329            name -- str
 330
 331            width -- int
 332
 333            height -- int
 334
 335            protected -- bool
 336
 337            protection_key -- str
 338
 339            display -- bool
 340
 341            printable -- bool
 342
 343            print_ranges -- list
 344
 345            style -- str
 346        """
 347        super().__init__(**kwargs)
 348        self._indexes = {}
 349        self._indexes["_cmap"] = {}
 350        self._indexes["_tmap"] = {}
 351        if self._do_init:
 352            self.name = name
 353            if protected:
 354                self.protected = protected
 355                self.set_protection_key = protection_key
 356            if not display:
 357                self.displayed = display
 358            if not printable:
 359                self.printable = printable
 360            if print_ranges:
 361                self.print_ranges = print_ranges
 362            if style:
 363                self.style = style
 364            # Prefill the table
 365            if width is not None or height is not None:
 366                width = width or 1
 367                height = height or 1
 368                # Column groups for style information
 369                columns = Column(repeated=width)
 370                self._append(columns)
 371                for _i in range(height):
 372                    row = Row(width)
 373                    self._append(row)
 374        self._compute_table_cache()
 375
 376    def __str__(self) -> str:
 377        def write_content(csv_writer: object) -> None:
 378            for values in self.iter_values():
 379                line = []
 380                for value in values:
 381                    if value is None:
 382                        value = ""
 383                    if isinstance(value, str):
 384                        value = value.strip()
 385                    line.append(value)
 386                csv_writer.writerow(line)  # type: ignore
 387
 388        out = StringIO(newline=os.linesep)
 389        csv_writer = csv.writer(
 390            out,
 391            delimiter=" ",
 392            doublequote=False,
 393            escapechar="\\",
 394            lineterminator=os.linesep,
 395            quotechar='"',
 396            quoting=csv.QUOTE_NONNUMERIC,
 397        )
 398        write_content(csv_writer)
 399        return out.getvalue()
 400
 401    def _translate_y_from_any(self, y: str | int) -> int:
 402        # "3" (couting from 1) -> 2 (couting from 0)
 403        return translate_from_any(y, self.height, 1)
 404
 405    def _translate_table_coordinates_list(
 406        self,
 407        coord: tuple | list,
 408    ) -> tuple[int | None, ...]:
 409        height = self.height
 410        width = self.width
 411        # assuming we got int values
 412        if len(coord) == 1:
 413            # It is a row
 414            y = coord[0]
 415            if y and y < 0:
 416                y = increment(y, height)
 417            return (None, y, None, y)
 418        if len(coord) == 2:
 419            # It is a row range, not a cell, because context is table
 420            y = coord[0]
 421            if y and y < 0:
 422                y = increment(y, height)
 423            t = coord[1]
 424            if t and t < 0:
 425                t = increment(t, height)
 426            return (None, y, None, t)
 427        # should be 4 int
 428        x, y, z, t = coord
 429        if x and x < 0:
 430            x = increment(x, width)
 431        if y and y < 0:
 432            y = increment(y, height)
 433        if z and z < 0:
 434            z = increment(z, width)
 435        if t and t < 0:
 436            t = increment(t, height)
 437        return (x, y, z, t)
 438
 439    def _translate_table_coordinates_str(
 440        self,
 441        coord_str: str,
 442    ) -> tuple[int | None, ...]:
 443        height = self.height
 444        width = self.width
 445        coord = convert_coordinates(coord_str)
 446        if len(coord) == 2:
 447            x, y = coord
 448            if x and x < 0:
 449                x = increment(x, width)
 450            if y and y < 0:
 451                y = increment(y, height)
 452            # extent to an area :
 453            return (x, y, x, y)
 454        x, y, z, t = coord
 455        if x and x < 0:
 456            x = increment(x, width)
 457        if y and y < 0:
 458            y = increment(y, height)
 459        if z and z < 0:
 460            z = increment(z, width)
 461        if t and t < 0:
 462            t = increment(t, height)
 463        return (x, y, z, t)
 464
 465    def _translate_table_coordinates(
 466        self,
 467        coord: tuple | list | str,
 468    ) -> tuple[int | None, ...]:
 469        if isinstance(coord, str):
 470            return self._translate_table_coordinates_str(coord)
 471        return self._translate_table_coordinates_list(coord)
 472
 473    def _translate_column_coordinates_str(
 474        self,
 475        coord_str: str,
 476    ) -> tuple[int | None, ...]:
 477        width = self.width
 478        height = self.height
 479        coord = convert_coordinates(coord_str)
 480        if len(coord) == 2:
 481            x, y = coord
 482            if x and x < 0:
 483                x = increment(x, width)
 484            if y and y < 0:
 485                y = increment(y, height)
 486            # extent to an area :
 487            return (x, y, x, y)
 488        x, y, z, t = coord
 489        if x and x < 0:
 490            x = increment(x, width)
 491        if y and y < 0:
 492            y = increment(y, height)
 493        if z and z < 0:
 494            z = increment(z, width)
 495        if t and t < 0:
 496            t = increment(t, height)
 497        return (x, y, z, t)
 498
 499    def _translate_column_coordinates_list(
 500        self,
 501        coord: tuple | list,
 502    ) -> tuple[int | None, ...]:
 503        width = self.width
 504        height = self.height
 505        # assuming we got int values
 506        if len(coord) == 1:
 507            # It is a column
 508            x = coord[0]
 509            if x and x < 0:
 510                x = increment(x, width)
 511            return (x, None, x, None)
 512        if len(coord) == 2:
 513            # It is a column range, not a cell, because context is table
 514            x = coord[0]
 515            if x and x < 0:
 516                x = increment(x, width)
 517            z = coord[1]
 518            if z and z < 0:
 519                z = increment(z, width)
 520            return (x, None, z, None)
 521        # should be 4 int
 522        x, y, z, t = coord
 523        if x and x < 0:
 524            x = increment(x, width)
 525        if y and y < 0:
 526            y = increment(y, height)
 527        if z and z < 0:
 528            z = increment(z, width)
 529        if t and t < 0:
 530            t = increment(t, height)
 531        return (x, y, z, t)
 532
 533    def _translate_column_coordinates(
 534        self,
 535        coord: tuple | list | str,
 536    ) -> tuple[int | None, ...]:
 537        if isinstance(coord, str):
 538            return self._translate_column_coordinates_str(coord)
 539        return self._translate_column_coordinates_list(coord)
 540
 541    def _translate_cell_coordinates(
 542        self,
 543        coord: tuple | list | str,
 544    ) -> tuple[int | None, int | None]:
 545        # we want an x,y result
 546        coord = convert_coordinates(coord)
 547        if len(coord) == 2:
 548            x, y = coord
 549        # If we got an area, take the first cell
 550        elif len(coord) == 4:
 551            x, y, z, t = coord
 552        else:
 553            raise ValueError(str(coord))
 554        if x and x < 0:
 555            x = increment(x, self.width)
 556        if y and y < 0:
 557            y = increment(y, self.height)
 558        return (x, y)
 559
 560    def _compute_table_cache(self) -> None:
 561        idx_repeated_seq = self.elements_repeated_sequence(
 562            _xpath_row, "table:number-rows-repeated"
 563        )
 564        self._tmap = make_cache_map(idx_repeated_seq)
 565        idx_repeated_seq = self.elements_repeated_sequence(
 566            _xpath_column, "table:number-columns-repeated"
 567        )
 568        self._cmap = make_cache_map(idx_repeated_seq)
 569
 570    def _update_width(self, row: Row) -> None:
 571        """Synchronize the number of columns if the row is bigger.
 572
 573        Append, don't insert, not to disturb the current layout.
 574        """
 575        diff = row.width - self.width
 576        if diff > 0:
 577            self.append_column(Column(repeated=diff))
 578
 579    def _get_formatted_text_normal(self, context: dict | None) -> str:
 580        result = []
 581        for row in self.traverse():
 582            for cell in row.traverse():
 583                value = cell.get_value(try_get_text=False)
 584                # None ?
 585                if value is None:
 586                    # Try with get_formatted_text on the elements
 587                    value = []
 588                    for element in cell.children:
 589                        value.append(element.get_formatted_text(context))
 590                    value = "".join(value)
 591                else:
 592                    value = str(value)
 593                result.append(value)
 594                result.append("\n")
 595            result.append("\n")
 596        return "".join(result)
 597
 598    def _get_formatted_text_rst(self, context: dict) -> str:  # noqa: C901
 599        context["no_img_level"] += 1
 600        # Strip the table => We must clone
 601        table = self.clone
 602        table.rstrip(aggressive=True)  # type: ignore
 603
 604        # Fill the rows
 605        rows = []
 606        cols_nb = 0
 607        cols_size: dict[int, int] = {}
 608        for odf_row in table.traverse():  # type: ignore
 609            row = []
 610            for i, cell in enumerate(odf_row.traverse()):
 611                value = cell.get_value(try_get_text=False)
 612                # None ?
 613                if value is None:
 614                    # Try with get_formatted_text on the elements
 615                    value = []
 616                    for element in cell.children:
 617                        value.append(element.get_formatted_text(context))
 618                    value = "".join(value)
 619                else:
 620                    value = str(value)
 621                value = value.strip()
 622                # Strip the empty columns
 623                if value:
 624                    cols_nb = max(cols_nb, i + 1)
 625                # Compute the size of each columns (at least 2)
 626                cols_size[i] = max(cols_size.get(i, 2), len(value))
 627                # Append
 628                row.append(value)
 629            rows.append(row)
 630
 631        # Nothing ?
 632        if cols_nb == 0:
 633            return ""
 634
 635        # Prevent a crash with empty columns (by example with images)
 636        for col, size in cols_size.items():
 637            if size == 0:
 638                cols_size[col] = 1
 639
 640        # Update cols_size
 641        LINE_MAX = 100
 642        COL_MIN = 16
 643
 644        free_size = LINE_MAX - (cols_nb - 1) * 3 - 4
 645        real_size = sum([cols_size[i] for i in range(cols_nb)])
 646        if real_size > free_size:
 647            factor = float(free_size) / real_size
 648
 649            for i in range(cols_nb):
 650                old_size = cols_size[i]
 651
 652                # The cell is already small
 653                if old_size <= COL_MIN:
 654                    continue
 655
 656                new_size = int(factor * old_size)
 657
 658                if new_size < COL_MIN:
 659                    new_size = COL_MIN
 660                cols_size[i] = new_size
 661
 662        # Convert !
 663        result: list[str] = [""]
 664        # Construct the first/last line
 665        line: list[str] = []
 666        for i in range(cols_nb):
 667            line.append("=" * cols_size[i])
 668            line.append(" ")
 669        line_str = "".join(line)
 670
 671        # Add the lines
 672        result.append(line_str)
 673        for row in rows:
 674            # Wrap the row
 675            wrapped_row = []
 676            for i, value in enumerate(row[:cols_nb]):
 677                wrapped_value = []
 678                for part in value.split("\n"):
 679                    # Hack to handle correctly the lists or the directives
 680                    subsequent_indent = ""
 681                    part_lstripped = part.lstrip()
 682                    if part_lstripped.startswith("-") or part_lstripped.startswith(
 683                        ".."
 684                    ):
 685                        subsequent_indent = " " * (len(part) - len(part.lstrip()) + 2)
 686                    wrapped_part = wrap(
 687                        part, width=cols_size[i], subsequent_indent=subsequent_indent
 688                    )
 689                    if wrapped_part:
 690                        wrapped_value.extend(wrapped_part)
 691                    else:
 692                        wrapped_value.append("")
 693                wrapped_row.append(wrapped_value)
 694
 695            # Append!
 696            for j in range(max([1] + [len(values) for values in wrapped_row])):
 697                txt_row: list[str] = []
 698                for i in range(cols_nb):
 699                    values = wrapped_row[i] if i < len(wrapped_row) else []
 700
 701                    # An empty cell ?
 702                    if len(values) - 1 < j or not values[j]:
 703                        if i == 0 and j == 0:
 704                            txt_row.append("..")
 705                            txt_row.append(" " * (cols_size[i] - 1))
 706                        else:
 707                            txt_row.append(" " * (cols_size[i] + 1))
 708                        continue
 709
 710                    # Not empty
 711                    value = values[j]
 712                    txt_row.append(value)
 713                    txt_row.append(" " * (cols_size[i] - len(value) + 1))
 714                result.append("".join(txt_row))
 715
 716        result.append(line_str)
 717        result.append("")
 718        result.append("")
 719        result_str = "\n".join(result)
 720
 721        context["no_img_level"] -= 1
 722        return result_str
 723
 724    def _translate_x_from_any(self, x: str | int) -> int:
 725        return translate_from_any(x, self.width, 0)
 726
 727    #
 728    # Public API
 729    #
 730
 731    def append(self, something: Element | str) -> None:
 732        """Dispatch .append() call to append_row() or append_column()."""
 733        if isinstance(something, Row):
 734            self.append_row(something)
 735        elif isinstance(something, Column):
 736            self.append_column(something)
 737        else:
 738            # probably still an error
 739            self._append(something)
 740
 741    @property
 742    def height(self) -> int:
 743        """Get the current height of the table.
 744
 745        Return: int
 746        """
 747        try:
 748            height = self._tmap[-1] + 1
 749        except Exception:
 750            height = 0
 751        return height
 752
 753    @property
 754    def width(self) -> int:
 755        """Get the current width of the table, measured on columns.
 756
 757        Rows may have different widths, use the Table API to ensure width
 758        consistency.
 759
 760        Return: int
 761        """
 762        # Columns are our reference for user expected width
 763
 764        try:
 765            width = self._cmap[-1] + 1
 766        except Exception:
 767            width = 0
 768
 769        # columns = self._get_columns()
 770        # repeated = self.xpath(
 771        #        'table:table-column/@table:number-columns-repeated')
 772        # unrepeated = len(columns) - len(repeated)
 773        # ws = sum(int(r) for r in repeated) + unrepeated
 774        # if w != ws:
 775        #    print "WARNING   ws", ws, "w", w
 776
 777        return width
 778
 779    @property
 780    def size(self) -> tuple[int, int]:
 781        """Shortcut to get the current width and height of the table.
 782
 783        Return: (int, int)
 784        """
 785        return self.width, self.height
 786
 787    @property
 788    def name(self) -> str | None:
 789        """Get / set the name of the table."""
 790        return self.get_attribute_string("table:name")
 791
 792    @name.setter
 793    def name(self, name: str) -> None:
 794        name = _table_name_check(name)
 795        # first, update named ranges
 796        # fixme : delete name ranges when deleting table, too.
 797        for named_range in self.get_named_ranges(table_name=self.name):
 798            named_range.set_table_name(name)
 799        self.set_attribute("table:name", name)
 800
 801    @property
 802    def protected(self) -> bool:
 803        return bool(self.get_attribute("table:protected"))
 804
 805    @protected.setter
 806    def protected(self, protect: bool) -> None:
 807        self.set_attribute("table:protected", protect)
 808
 809    @property
 810    def protection_key(self) -> str | None:
 811        return self.get_attribute_string("table:protection-key")
 812
 813    @protection_key.setter
 814    def protection_key(self, key: str) -> None:
 815        self.set_attribute("table:protection-key", key)
 816
 817    @property
 818    def displayed(self) -> bool:
 819        return bool(self.get_attribute("table:display"))
 820
 821    @displayed.setter
 822    def displayed(self, display: bool) -> None:
 823        self.set_attribute("table:display", display)
 824
 825    @property
 826    def printable(self) -> bool:
 827        printable = self.get_attribute("table:print")
 828        # Default value
 829        if printable is None:
 830            return True
 831        return bool(printable)
 832
 833    @printable.setter
 834    def printable(self, printable: bool) -> None:
 835        self.set_attribute("table:print", printable)
 836
 837    @property
 838    def print_ranges(self) -> list[str]:
 839        ranges = self.get_attribute_string("table:print-ranges")
 840        if isinstance(ranges, str):
 841            return ranges.split()
 842        return []
 843
 844    @print_ranges.setter
 845    def print_ranges(self, ranges: list[str] | None) -> None:
 846        if isinstance(ranges, (list, tuple)):
 847            self.set_attribute("table:print-ranges", " ".join(ranges))
 848        else:
 849            self.set_attribute("table:print-ranges", ranges)
 850
 851    @property
 852    def style(self) -> str | None:
 853        """Get / set the style of the table
 854
 855        Return: str
 856        """
 857        return self.get_attribute_string("table:style-name")
 858
 859    @style.setter
 860    def style(self, style: str | Element) -> None:
 861        self.set_style_attribute("table:style-name", style)
 862
 863    def get_formatted_text(self, context: dict | None = None) -> str:
 864        if context and context["rst_mode"]:
 865            return self._get_formatted_text_rst(context)
 866        return self._get_formatted_text_normal(context)
 867
 868    def get_values(
 869        self,
 870        coord: tuple | list | str | None = None,
 871        cell_type: str | None = None,
 872        complete: bool = True,
 873        get_type: bool = False,
 874        flat: bool = False,
 875    ) -> list:
 876        """Get a matrix of values of the table.
 877
 878        Filter by coordinates will parse the area defined by the coordinates.
 879
 880        If 'cell_type' is used and 'complete' is True (default), missing values
 881        are replaced by None.
 882        Filter by ' cell_type = "all" ' will retrieve cells of any
 883        type, aka non empty cells.
 884
 885        If 'cell_type' is None, complete is always True : with no cell type
 886        queried, get_values() returns None for each empty cell, the length
 887        each lists is equal to the width of the table.
 888
 889        If get_type is True, returns tuples (value, ODF type of value), or
 890        (None, None) for empty cells with complete True.
 891
 892        If flat is True, the methods return a single list of all the values.
 893        By default, flat is False.
 894
 895        Arguments:
 896
 897            coord -- str or tuple of int : coordinates of area
 898
 899            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
 900                         'currency', 'percentage' or 'all'
 901
 902            complete -- boolean
 903
 904            get_type -- boolean
 905
 906        Return: list of lists of Python types
 907        """
 908        if coord:
 909            x, y, z, t = self._translate_table_coordinates(coord)
 910        else:
 911            x = y = z = t = None
 912        data = []
 913        for row in self.traverse(start=y, end=t):
 914            if z is None:
 915                width = self.width
 916            else:
 917                width = min(z + 1, self.width)
 918            if x is not None:
 919                width -= x
 920            values = row.get_values(
 921                (x, z),
 922                cell_type=cell_type,
 923                complete=complete,
 924                get_type=get_type,
 925            )
 926            # complete row to match request width
 927            if complete:
 928                if get_type:
 929                    values.extend([(None, None)] * (width - len(values)))
 930                else:
 931                    values.extend([None] * (width - len(values)))
 932            if flat:
 933                data.extend(values)
 934            else:
 935                data.append(values)
 936        return data
 937
 938    def iter_values(
 939        self,
 940        coord: tuple | list | str | None = None,
 941        cell_type: str | None = None,
 942        complete: bool = True,
 943        get_type: bool = False,
 944    ) -> Iterator[list]:
 945        """Iterate through lines of Python values of the table.
 946
 947        Filter by coordinates will parse the area defined by the coordinates.
 948
 949        cell_type, complete, grt_type : see get_values()
 950
 951
 952
 953        Arguments:
 954
 955            coord -- str or tuple of int : coordinates of area
 956
 957            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
 958                         'currency', 'percentage' or 'all'
 959
 960            complete -- boolean
 961
 962            get_type -- boolean
 963
 964        Return: iterator of lists
 965        """
 966        if coord:
 967            x, y, z, t = self._translate_table_coordinates(coord)
 968        else:
 969            x = y = z = t = None
 970        for row in self.traverse(start=y, end=t):
 971            if z is None:
 972                width = self.width
 973            else:
 974                width = min(z + 1, self.width)
 975            if x is not None:
 976                width -= x
 977            values = row.get_values(
 978                (x, z),
 979                cell_type=cell_type,
 980                complete=complete,
 981                get_type=get_type,
 982            )
 983            # complete row to match column width
 984            if complete:
 985                if get_type:
 986                    values.extend([(None, None)] * (width - len(values)))
 987                else:
 988                    values.extend([None] * (width - len(values)))
 989            yield values
 990
 991    def set_values(
 992        self,
 993        values: list,
 994        coord: tuple | list | str | None = None,
 995        style: str | None = None,
 996        cell_type: str | None = None,
 997        currency: str | None = None,
 998    ) -> None:
 999        """Set the value of cells in the table, from the 'coord' position
1000        with values.
1001
1002        'coord' is the coordinate of the upper left cell to be modified by
1003        values. If 'coord' is None, default to the position (0,0) ("A1").
1004        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1005        area is used as coordinate.
1006
1007        The table is *not* cleared before the operation, to reset the table
1008        before setting values, use table.clear().
1009
1010        A list of lists is expected, with as many lists as rows, and as many
1011        items in each sublist as cells to be setted. None values in the list
1012        will create empty cells with no cell type (but eventually a style).
1013
1014        Arguments:
1015
1016            coord -- tuple or str
1017
1018            values -- list of lists of python types
1019
1020            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1021                         'string' or 'time'
1022
1023            currency -- three-letter str
1024
1025            style -- str
1026        """
1027        if coord:
1028            x, y = self._translate_cell_coordinates(coord)
1029        else:
1030            x = y = 0
1031        if y is None:
1032            y = 0
1033        if x is None:
1034            x = 0
1035        y -= 1
1036        for row_values in values:
1037            y += 1
1038            if not row_values:
1039                continue
1040            row = self.get_row(y, clone=True)
1041            repeated = row.repeated or 1
1042            if repeated >= 2:
1043                row.repeated = None
1044            row.set_values(
1045                row_values,
1046                start=x,
1047                cell_type=cell_type,
1048                currency=currency,
1049                style=style,
1050            )
1051            self.set_row(y, row, clone=False)
1052            self._update_width(row)
1053
1054    def rstrip(self, aggressive: bool = False) -> None:
1055        """Remove *in-place* empty rows below and empty cells at the right of
1056        the table. Cells are empty if they contain no value or it evaluates
1057        to False, and no style.
1058
1059        If aggressive is True, empty cells with style are removed too.
1060
1061        Argument:
1062
1063            aggressive -- bool
1064        """
1065        # Step 1: remove empty rows below the table
1066        for row in reversed(self._get_rows()):
1067            if row.is_empty(aggressive=aggressive):
1068                row.parent.delete(row)  # type: ignore
1069            else:
1070                break
1071        # Step 2: rstrip remaining rows
1072        max_width = 0
1073        for row in self._get_rows():
1074            row.rstrip(aggressive=aggressive)
1075            # keep count of the biggest row
1076            max_width = max(max_width, row.width)
1077        # raz cache of rows
1078        self._indexes["_tmap"] = {}
1079        # Step 3: trim columns to match max_width
1080        columns = self._get_columns()
1081        repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated")
1082        if not isinstance(repeated_cols, list):
1083            raise TypeError
1084        unrepeated = len(columns) - len(repeated_cols)
1085        column_width = sum(int(r) for r in repeated_cols) + unrepeated  # type: ignore
1086        diff = column_width - max_width
1087        if diff > 0:
1088            for column in reversed(columns):
1089                repeated = column.repeated or 1
1090                repeated = repeated - diff
1091                if repeated > 0:
1092                    column.repeated = repeated
1093                    break
1094                else:
1095                    column.parent.delete(column)
1096                    diff = -repeated
1097                    if diff == 0:
1098                        break
1099        # raz cache of columns
1100        self._indexes["_cmap"] = {}
1101        self._compute_table_cache()
1102
1103    def transpose(self, coord: tuple | list | str | None = None) -> None:  # noqa: C901
1104        """Swap *in-place* rows and columns of the table.
1105
1106        If 'coord' is not None, apply transpose only to the area defined by the
1107        coordinates. Beware, if area is not square, some cells mays be over
1108        written during the process.
1109
1110        Arguments:
1111
1112            coord -- str or tuple of int : coordinates of area
1113
1114            start -- int or str
1115        """
1116        data = []
1117        if coord is None:
1118            for row in self.traverse():
1119                data.append(list(row.traverse()))
1120            transposed_data = zip_longest(*data)
1121            self.clear()
1122            # new_rows = []
1123            for row_cells in transposed_data:
1124                if not isiterable(row_cells):
1125                    row_cells = (row_cells,)
1126                row = Row()
1127                row.extend_cells(row_cells)
1128                self.append_row(row, clone=False)
1129            self._compute_table_cache()
1130        else:
1131            x, y, z, t = self._translate_table_coordinates(coord)
1132            if x is None:
1133                x = 0
1134            else:
1135                x = min(x, self.width - 1)
1136            if z is None:
1137                z = self.width - 1
1138            else:
1139                z = min(z, self.width - 1)
1140            if y is None:
1141                y = 0
1142            else:
1143                y = min(y, self.height - 1)
1144            if t is None:
1145                t = self.height - 1
1146            else:
1147                t = min(t, self.height - 1)
1148            for row in self.traverse(start=y, end=t):
1149                data.append(list(row.traverse(start=x, end=z)))
1150            transposed_data = zip_longest(*data)
1151            # clear locally
1152            w = z - x + 1
1153            h = t - y + 1
1154            if w != h:
1155                nones = [[None] * w for i in range(h)]
1156                self.set_values(nones, coord=(x, y, z, t))
1157            # put transposed
1158            filtered_data: list[tuple[Cell]] = []
1159            for row_cells in transposed_data:
1160                if isinstance(row_cells, (list, tuple)):
1161                    filtered_data.append(row_cells)
1162                else:
1163                    filtered_data.append((row_cells,))
1164            self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1))
1165            self._compute_table_cache()
1166
1167    def is_empty(self, aggressive: bool = False) -> bool:
1168        """Return whether every cell in the table has no value or the value
1169        evaluates to False (empty string), and no style.
1170
1171        If aggressive is True, empty cells with style are considered empty.
1172
1173        Arguments:
1174
1175            aggressive -- bool
1176        """
1177        return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())
1178
1179    #
1180    # Rows
1181    #
1182
1183    def _get_rows(self) -> list[Row]:
1184        return self.get_elements(_xpath_row)  # type: ignore
1185
1186    def traverse(  # noqa: C901
1187        self,
1188        start: int | None = None,
1189        end: int | None = None,
1190    ) -> Iterator[Row]:
1191        """Yield as many row elements as expected rows in the table, i.e.
1192        expand repetitions by returning the same row as many times as
1193        necessary.
1194
1195            Arguments:
1196
1197                start -- int
1198
1199                end -- int
1200
1201        Copies are returned, use set_row() to push them back.
1202        """
1203        idx = -1
1204        before = -1
1205        y = 0
1206        if start is None and end is None:
1207            for juska in self._tmap:
1208                idx += 1
1209                if idx in self._indexes["_tmap"]:
1210                    row = self._indexes["_tmap"][idx]
1211                else:
1212                    row = self._get_element_idx2(_xpath_row_idx, idx)
1213                    self._indexes["_tmap"][idx] = row
1214                repeated = juska - before
1215                before = juska
1216                for _i in range(repeated or 1):
1217                    # Return a copy without the now obsolete repetition
1218                    row = row.clone
1219                    row.y = y
1220                    y += 1
1221                    if repeated > 1:
1222                        row.repeated = None
1223                    yield row
1224        else:
1225            if start is None:
1226                start = 0
1227            start = max(0, start)
1228            if end is None:
1229                try:
1230                    end = self._tmap[-1]
1231                except Exception:
1232                    end = -1
1233            start_map = find_odf_idx(self._tmap, start)
1234            if start_map is None:
1235                return
1236            if start_map > 0:
1237                before = self._tmap[start_map - 1]
1238            idx = start_map - 1
1239            before = start - 1
1240            y = start
1241            for juska in self._tmap[start_map:]:
1242                idx += 1
1243                if idx in self._indexes["_tmap"]:
1244                    row = self._indexes["_tmap"][idx]
1245                else:
1246                    row = self._get_element_idx2(_xpath_row_idx, idx)
1247                    self._indexes["_tmap"][idx] = row
1248                repeated = juska - before
1249                before = juska
1250                for _i in range(repeated or 1):
1251                    if y <= end:
1252                        row = row.clone
1253                        row.y = y
1254                        y += 1
1255                        if repeated > 1 or (y == start and start > 0):
1256                            row.repeated = None
1257                        yield row
1258
1259    def get_rows(
1260        self,
1261        coord: tuple | list | str | None = None,
1262        style: str | None = None,
1263        content: str | None = None,
1264    ) -> list[Row]:
1265        """Get the list of rows matching the criteria.
1266
1267        Filter by coordinates will parse the area defined by the coordinates.
1268
1269        Arguments:
1270
1271            coord -- str or tuple of int : coordinates of rows
1272
1273            content -- str regex
1274
1275            style -- str
1276
1277        Return: list of rows
1278        """
1279        if coord:
1280            _x, y, _z, t = self._translate_table_coordinates(coord)
1281        else:
1282            y = t = None
1283        # fixme : not clones ?
1284        if not content and not style:
1285            return list(self.traverse(start=y, end=t))
1286        rows = []
1287        for row in self.traverse(start=y, end=t):
1288            if content and not row.match(content):
1289                continue
1290            if style and style != row.style:
1291                continue
1292            rows.append(row)
1293        return rows
1294
1295    def _get_row2(self, y: int, clone: bool = True, create: bool = True) -> Row:
1296        if y >= self.height:
1297            if create:
1298                return Row()
1299            raise ValueError("Row not found")
1300        row = self._get_row2_base(y)
1301        if row is None:
1302            raise ValueError("Row not found")
1303        if clone:
1304            return row.clone
1305        return row
1306
1307    def _get_row2_base(self, y: int) -> Row | None:
1308        idx = find_odf_idx(self._tmap, y)
1309        if idx is not None:
1310            if idx in self._indexes["_tmap"]:
1311                row = self._indexes["_tmap"][idx]
1312            else:
1313                row = self._get_element_idx2(_xpath_row_idx, idx)
1314                self._indexes["_tmap"][idx] = row
1315            return row
1316        return None
1317
1318    def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1319        """Get the row at the given "y" position.
1320
1321        Position start at 0. So cell A4 is on row 3.
1322
1323        A copy is returned, use set_cell() to push it back.
1324
1325        Arguments:
1326
1327            y -- int or str
1328
1329        Return: Row
1330        """
1331        # fixme : keep repeat ? maybe an option to functions : "raw=False"
1332        y = self._translate_y_from_any(y)
1333        row = self._get_row2(y, clone=clone, create=create)
1334        if row is None:
1335            raise ValueError("Row not found")
1336        row.y = y
1337        return row
1338
1339    def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1340        """Replace the row at the given position with the new one. Repetions of
1341        the old row will be adjusted.
1342
1343        If row is None, a new empty row is created.
1344
1345        Position start at 0. So cell A4 is on row 3.
1346
1347        Arguments:
1348
1349            y -- int or str
1350
1351            row -- Row
1352
1353        returns the row, with updated row.y
1354        """
1355        if row is None:
1356            row = Row()
1357            repeated = 1
1358            clone = False
1359        else:
1360            repeated = row.repeated or 1
1361        y = self._translate_y_from_any(y)
1362        row.y = y
1363        # Outside the defined table ?
1364        diff = y - self.height
1365        if diff == 0:
1366            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1367        elif diff > 0:
1368            self.append_row(Row(repeated=diff), _repeated=diff, clone=clone)
1369            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1370        else:
1371            # Inside the defined table
1372            row_back = set_item_in_vault(  # type: ignore
1373                y, row, self, _xpath_row_idx, "_tmap", clone=clone
1374            )
1375        # print self.serialize(True)
1376        # Update width if necessary
1377        self._update_width(row_back)
1378        return row_back
1379
1380    def insert_row(
1381        self, y: str | int, row: Row | None = None, clone: bool = True
1382    ) -> Row:
1383        """Insert the row before the given "y" position. If no row is given,
1384        an empty one is created.
1385
1386        Position start at 0. So cell A4 is on row 3.
1387
1388        If row is None, a new empty row is created.
1389
1390        Arguments:
1391
1392            y -- int or str
1393
1394            row -- Row
1395
1396        returns the row, with updated row.y
1397        """
1398        if row is None:
1399            row = Row()
1400            clone = False
1401        y = self._translate_y_from_any(y)
1402        diff = y - self.height
1403        if diff < 0:
1404            row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap")
1405        elif diff == 0:
1406            row_back = self.append_row(row, clone=clone)
1407        else:
1408            self.append_row(Row(repeated=diff), _repeated=diff, clone=False)
1409            row_back = self.append_row(row, clone=clone)
1410        row_back.y = y  # type: ignore
1411        # Update width if necessary
1412        self._update_width(row_back)  # type: ignore
1413        return row_back  # type: ignore
1414
1415    def extend_rows(self, rows: list[Row] | None = None) -> None:
1416        """Append a list of rows at the end of the table.
1417
1418        Arguments:
1419
1420            rows -- list of Row
1421        """
1422        if rows is None:
1423            rows = []
1424        self.extend(rows)
1425        self._compute_table_cache()
1426        # Update width if necessary
1427        width = self.width
1428        for row in self.traverse():
1429            if row.width > width:
1430                width = row.width
1431        diff = width - self.width
1432        if diff > 0:
1433            self.append_column(Column(repeated=diff))
1434
1435    def append_row(
1436        self,
1437        row: Row | None = None,
1438        clone: bool = True,
1439        _repeated: int | None = None,
1440    ) -> Row:
1441        """Append the row at the end of the table. If no row is given, an
1442        empty one is created.
1443
1444        Position start at 0. So cell A4 is on row 3.
1445
1446        Note the columns are automatically created when the first row is
1447        inserted in an empty table. So better insert a filled row.
1448
1449        Arguments:
1450
1451            row -- Row
1452
1453            _repeated -- (optional), repeated value of the row
1454
1455        returns the row, with updated row.y
1456        """
1457        if row is None:
1458            row = Row()
1459            _repeated = 1
1460        elif clone:
1461            row = row.clone
1462        # Appending a repeated row accepted
1463        # Do not insert next to the last row because it could be in a group
1464        self._append(row)
1465        if _repeated is None:
1466            _repeated = row.repeated or 1
1467        self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated)
1468        row.y = self.height - 1
1469        # Initialize columns
1470        if not self._get_columns():
1471            repeated = row.width
1472            self.insert(Column(repeated=repeated), position=0)
1473            self._compute_table_cache()
1474        # Update width if necessary
1475        self._update_width(row)
1476        return row
1477
1478    def delete_row(self, y: int | str) -> None:
1479        """Delete the row at the given "y" position.
1480
1481        Position start at 0. So cell A4 is on row 3.
1482
1483        Arguments:
1484
1485            y -- int or str
1486        """
1487        y = self._translate_y_from_any(y)
1488        # Outside the defined table
1489        if y >= self.height:
1490            return
1491        # Inside the defined table
1492        delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")
1493
1494    def get_row_values(
1495        self,
1496        y: int | str,
1497        cell_type: str | None = None,
1498        complete: bool = True,
1499        get_type: bool = False,
1500    ) -> list:
1501        """Shortcut to get the list of Python values for the cells of the row
1502        at the given "y" position.
1503
1504        Position start at 0. So cell A4 is on row 3.
1505
1506        Filter by cell_type, with cell_type 'all' will retrieve cells of any
1507        type, aka non empty cells.
1508        If cell_type and complete is True, replace missing values by None.
1509
1510        If get_type is True, returns a tuple (value, ODF type of value)
1511
1512        Arguments:
1513
1514            y -- int, str
1515
1516            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1517                         'currency', 'percentage' or 'all'
1518
1519            complete -- boolean
1520
1521            get_type -- boolean
1522
1523        Return: list of lists of Python types
1524        """
1525        values = self.get_row(y, clone=False).get_values(
1526            cell_type=cell_type, complete=complete, get_type=get_type
1527        )
1528        # complete row to match column width
1529        if complete:
1530            if get_type:
1531                values.extend([(None, None)] * (self.width - len(values)))
1532            else:
1533                values.extend([None] * (self.width - len(values)))
1534        return values
1535
1536    def set_row_values(
1537        self,
1538        y: int | str,
1539        values: list,
1540        cell_type: str | None = None,
1541        currency: str | None = None,
1542        style: str | None = None,
1543    ) -> Row:
1544        """Shortcut to set the values of *all* cells of the row at the given
1545        "y" position.
1546
1547        Position start at 0. So cell A4 is on row 3.
1548
1549        Arguments:
1550
1551            y -- int or str
1552
1553            values -- list of Python types
1554
1555            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1556                         'string' or 'time'
1557
1558            currency -- three-letter str
1559
1560            style -- str
1561
1562        returns the row, with updated row.y
1563        """
1564        row = Row()  # needed if clones rows
1565        row.set_values(values, style=style, cell_type=cell_type, currency=currency)
1566        return self.set_row(y, row)  # needed if clones rows
1567
1568    def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1569        """Shortcut to set *all* the cells of the row at the given
1570        "y" position.
1571
1572        Position start at 0. So cell A4 is on row 3.
1573
1574        Arguments:
1575
1576            y -- int or str
1577
1578            cells -- list of Python types
1579
1580            style -- str
1581
1582        returns the row, with updated row.y
1583        """
1584        if cells is None:
1585            cells = []
1586        row = Row()  # needed if clones rows
1587        row.extend_cells(cells)
1588        return self.set_row(y, row)  # needed if clones rows
1589
1590    def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1591        """Return wether every cell in the row at the given "y" position has
1592        no value or the value evaluates to False (empty string), and no style.
1593
1594        Position start at 0. So cell A4 is on row 3.
1595
1596        If aggressive is True, empty cells with style are considered empty.
1597
1598        Arguments:
1599
1600            y -- int or str
1601
1602            aggressive -- bool
1603        """
1604        return self.get_row(y, clone=False).is_empty(aggressive=aggressive)
1605
1606    #
1607    # Cells
1608    #
1609
1610    def get_cells(
1611        self,
1612        coord: tuple | list | str | None = None,
1613        cell_type: str | None = None,
1614        style: str | None = None,
1615        content: str | None = None,
1616        flat: bool = False,
1617    ) -> list:
1618        """Get the cells matching the criteria. If 'coord' is None,
1619        parse the whole table, else parse the area defined by 'coord'.
1620
1621        Filter by  cell_type = "all"  will retrieve cells of any
1622        type, aka non empty cells.
1623
1624        If flat is True (default is False), the method return a single list
1625        of all the values, else a list of lists of cells.
1626
1627        if cell_type, style and content are None, get_cells() will return
1628        the exact number of cells of the area, including empty cells.
1629
1630        Arguments:
1631
1632            coordinates -- str or tuple of int : coordinates of area
1633
1634            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1635                         'currency', 'percentage' or 'all'
1636
1637            content -- str regex
1638
1639            style -- str
1640
1641            flat -- boolean
1642
1643        Return: list of tuples
1644        """
1645        if coord:
1646            x, y, z, t = self._translate_table_coordinates(coord)
1647        else:
1648            x = y = z = t = None
1649        if flat:
1650            cells: list[Cell] = []
1651            for row in self.traverse(start=y, end=t):
1652                row_cells = row.get_cells(
1653                    coord=(x, z),
1654                    cell_type=cell_type,
1655                    style=style,
1656                    content=content,
1657                )
1658                cells.extend(row_cells)
1659            return cells
1660        else:
1661            lcells: list[list[Cell]] = []
1662            for row in self.traverse(start=y, end=t):
1663                row_cells = row.get_cells(
1664                    coord=(x, z),
1665                    cell_type=cell_type,
1666                    style=style,
1667                    content=content,
1668                )
1669                lcells.append(row_cells)
1670            return lcells
1671
1672    def get_cell(
1673        self,
1674        coord: tuple | list | str,
1675        clone: bool = True,
1676        keep_repeated: bool = True,
1677    ) -> Cell:
1678        """Get the cell at the given coordinates.
1679
1680        They are either a 2-uplet of (x, y) starting from 0, or a
1681        human-readable position like "C4".
1682
1683        A copy is returned, use ``set_cell`` to push it back.
1684
1685        Arguments:
1686
1687            coord -- (int, int) or str
1688
1689        Return: Cell
1690        """
1691        x, y = self._translate_cell_coordinates(coord)
1692        if x is None:
1693            raise ValueError
1694        if y is None:
1695            raise ValueError
1696        # Outside the defined table
1697        if y >= self.height:
1698            cell = Cell()
1699        else:
1700            # Inside the defined table
1701            row = self._get_row2_base(y)
1702            if row is None:
1703                raise ValueError
1704            read_cell = row.get_cell(x, clone=clone)
1705            if read_cell is None:
1706                raise ValueError
1707            cell = read_cell
1708            if not keep_repeated:
1709                repeated = cell.repeated or 1
1710                if repeated >= 2:
1711                    cell.repeated = None
1712        cell.x = x
1713        cell.y = y
1714        return cell
1715
1716    def get_value(
1717        self,
1718        coord: tuple | list | str,
1719        get_type: bool = False,
1720    ) -> Any:
1721        """Shortcut to get the Python value of the cell at the given
1722        coordinates.
1723
1724        If get_type is True, returns the tuples (value, ODF type)
1725
1726        coord is either a 2-uplet of (x, y) starting from 0, or a
1727        human-readable position like "C4". If an Area is given, the upper
1728        left position is used as coord.
1729
1730        Arguments:
1731
1732            coord -- (int, int) or str : coordinate
1733
1734        Return: Python type
1735        """
1736        x, y = self._translate_cell_coordinates(coord)
1737        if x is None:
1738            raise ValueError
1739        if y is None:
1740            raise ValueError
1741        # Outside the defined table
1742        if y >= self.height:
1743            if get_type:
1744                return (None, None)
1745            return None
1746        else:
1747            # Inside the defined table
1748            row = self._get_row2_base(y)
1749            if row is None:
1750                raise ValueError
1751            cell = row._get_cell2_base(x)
1752            if cell is None:
1753                if get_type:
1754                    return (None, None)
1755                return None
1756            return cell.get_value(get_type=get_type)
1757
1758    def set_cell(
1759        self,
1760        coord: tuple | list | str,
1761        cell: Cell | None = None,
1762        clone: bool = True,
1763    ) -> Cell:
1764        """Replace a cell of the table at the given coordinates.
1765
1766        They are either a 2-uplet of (x, y) starting from 0, or a
1767        human-readable position like "C4".
1768
1769        Arguments:
1770
1771            coord -- (int, int) or str : coordinate
1772
1773            cell -- Cell
1774
1775        return the cell, with x and y updated
1776        """
1777        if cell is None:
1778            cell = Cell()
1779            clone = False
1780        x, y = self._translate_cell_coordinates(coord)
1781        if x is None:
1782            raise ValueError
1783        if y is None:
1784            raise ValueError
1785        cell.x = x
1786        cell.y = y
1787        if y >= self.height:
1788            row = Row()
1789            cell_back = row.set_cell(x, cell, clone=clone)
1790            self.set_row(y, row, clone=False)
1791        else:
1792            row_read = self._get_row2_base(y)
1793            if row_read is None:
1794                raise ValueError
1795            row = row_read
1796            row.y = y
1797            repeated = row.repeated or 1
1798            if repeated > 1:
1799                row = row.clone
1800                row.repeated = None
1801                cell_back = row.set_cell(x, cell, clone=clone)
1802                self.set_row(y, row, clone=False)
1803            else:
1804                cell_back = row.set_cell(x, cell, clone=clone)
1805                # Update width if necessary, since we don't use set_row
1806                self._update_width(row)
1807        return cell_back
1808
1809    def set_cells(
1810        self,
1811        cells: list[list[Cell]] | list[tuple[Cell]],
1812        coord: tuple | list | str | None = None,
1813        clone: bool = True,
1814    ) -> None:
1815        """Set the cells in the table, from the 'coord' position.
1816
1817        'coord' is the coordinate of the upper left cell to be modified by
1818        values. If 'coord' is None, default to the position (0,0) ("A1").
1819        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1820        area is used as coordinate.
1821
1822        The table is *not* cleared before the operation, to reset the table
1823        before setting cells, use table.clear().
1824
1825        A list of lists is expected, with as many lists as rows to be set, and
1826        as many cell in each sublist as cells to be setted in the row.
1827
1828        Arguments:
1829
1830            cells -- list of list of cells
1831
1832            coord -- tuple or str
1833
1834            values -- list of lists of python types
1835        """
1836        if coord:
1837            x, y = self._translate_cell_coordinates(coord)
1838        else:
1839            x = y = 0
1840        if y is None:
1841            y = 0
1842        if x is None:
1843            x = 0
1844        y -= 1
1845        for row_cells in cells:
1846            y += 1
1847            if not row_cells:
1848                continue
1849            row = self.get_row(y, clone=True)
1850            repeated = row.repeated or 1
1851            if repeated >= 2:
1852                row.repeated = None
1853            row.set_cells(row_cells, start=x, clone=clone)
1854            self.set_row(y, row, clone=False)
1855            self._update_width(row)
1856
1857    def set_value(
1858        self,
1859        coord: tuple | list | str,
1860        value: Any,
1861        cell_type: str | None = None,
1862        currency: str | None = None,
1863        style: str | None = None,
1864    ) -> None:
1865        """Set the Python value of the cell at the given coordinates.
1866
1867        They are either a 2-uplet of (x, y) starting from 0, or a
1868        human-readable position like "C4".
1869
1870        Arguments:
1871
1872            coord -- (int, int) or str
1873
1874            value -- Python type
1875
1876            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1877                     'string' or 'time'
1878
1879            currency -- three-letter str
1880
1881            style -- str
1882
1883        """
1884        self.set_cell(
1885            coord,
1886            Cell(value, cell_type=cell_type, currency=currency, style=style),
1887            clone=False,
1888        )
1889
1890    def set_cell_image(
1891        self,
1892        coord: tuple | list | str,
1893        image_frame: Frame,
1894        doc_type: str | None = None,
1895    ) -> None:
1896        """Do all the magic to display an image in the cell at the given
1897        coordinates.
1898
1899        They are either a 2-uplet of (x, y) starting from 0, or a
1900        human-readable position like "C4".
1901
1902        The frame element must contain the expected image position and
1903        dimensions.
1904
1905        DrawImage insertion depends on the document type, so the type must be
1906        provided or the table element must be already attached to a document.
1907
1908        Arguments:
1909
1910            coord -- (int, int) or str
1911
1912            image_frame -- Frame including an image
1913
1914            doc_type -- 'spreadsheet' or 'text'
1915        """
1916        # Test document type
1917        if doc_type is None:
1918            body = self.document_body
1919            if body is None:
1920                raise ValueError("document type not found")
1921            doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get(
1922                body.tag
1923            )
1924            if doc_type is None:
1925                raise ValueError("document type not supported for images")
1926        # We need the end address of the image
1927        x, y = self._translate_cell_coordinates(coord)
1928        if x is None:
1929            raise ValueError
1930        if y is None:
1931            raise ValueError
1932        cell = self.get_cell((x, y))
1933        image_frame = image_frame.clone  # type: ignore
1934        # Remove any previous paragraph, frame, etc.
1935        for child in cell.children:
1936            cell.delete(child)
1937        # Now it all depends on the document type
1938        if doc_type == "spreadsheet":
1939            image_frame.anchor_type = "char"
1940            # The frame needs end coordinates
1941            width, height = image_frame.size
1942            image_frame.set_attribute("table:end-x", width)
1943            image_frame.set_attribute("table:end-y", height)
1944            # FIXME what happens when the address changes?
1945            address = f"{self.name}.{digit_to_alpha(x)}{y + 1}"
1946            image_frame.set_attribute("table:end-cell-address", address)
1947            # The frame is directly in the cell
1948            cell.append(image_frame)
1949        elif doc_type == "text":
1950            # The frame must be in a paragraph
1951            cell.set_value("")
1952            paragraph = cell.get_element("text:p")
1953            if paragraph is None:
1954                raise ValueError
1955            paragraph.append(image_frame)
1956        self.set_cell(coord, cell)
1957
1958    def insert_cell(
1959        self,
1960        coord: tuple | list | str,
1961        cell: Cell | None = None,
1962        clone: bool = True,
1963    ) -> Cell:
1964        """Insert the given cell at the given coordinates. If no cell is
1965        given, an empty one is created.
1966
1967        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
1968        human-readable position like "C4".
1969
1970        Cells on the right are shifted. Other rows remain untouched.
1971
1972        Arguments:
1973
1974            coord -- (int, int) or str
1975
1976            cell -- Cell
1977
1978        returns the cell with x and y updated
1979        """
1980        if cell is None:
1981            cell = Cell()
1982            clone = False
1983        if clone:
1984            cell = cell.clone
1985        x, y = self._translate_cell_coordinates(coord)
1986        if x is None:
1987            raise ValueError
1988        if y is None:
1989            raise ValueError
1990        row = self._get_row2(y, clone=True)
1991        row.y = y
1992        row.repeated = None
1993        cell_back = row.insert_cell(x, cell, clone=False)
1994        self.set_row(y, row, clone=False)
1995        # Update width if necessary
1996        self._update_width(row)
1997        return cell_back
1998
1999    def append_cell(
2000        self,
2001        y: int | str,
2002        cell: Cell | None = None,
2003        clone: bool = True,
2004    ) -> Cell:
2005        """Append the given cell at the "y" coordinate. Repeated cells are
2006        accepted. If no cell is given, an empty one is created.
2007
2008        Position start at 0. So cell A4 is on row 3.
2009
2010        Other rows remain untouched.
2011
2012        Arguments:
2013
2014            y -- int or str
2015
2016            cell -- Cell
2017
2018        returns the cell with x and y updated
2019        """
2020        if cell is None:
2021            cell = Cell()
2022            clone = False
2023        if clone:
2024            cell = cell.clone
2025        y = self._translate_y_from_any(y)
2026        row = self._get_row2(y)
2027        row.y = y
2028        cell_back = row.append_cell(cell, clone=False)
2029        self.set_row(y, row)
2030        # Update width if necessary
2031        self._update_width(row)
2032        return cell_back
2033
2034    def delete_cell(self, coord: tuple | list | str) -> None:
2035        """Delete the cell at the given coordinates, so that next cells are
2036        shifted to the left.
2037
2038        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2039        human-readable position like "C4".
2040
2041        Use set_value() for erasing value.
2042
2043        Arguments:
2044
2045            coord -- (int, int) or str
2046        """
2047        x, y = self._translate_cell_coordinates(coord)
2048        if x is None:
2049            raise ValueError
2050        if y is None:
2051            raise ValueError
2052        # Outside the defined table
2053        if y >= self.height:
2054            return
2055        # Inside the defined table
2056        row = self._get_row2_base(y)
2057        if row is None:
2058            raise ValueError
2059        row.delete_cell(x)
2060        # self.set_row(y, row)
2061
2062    # Columns
2063
2064    def _get_columns(self) -> list:
2065        return self.get_elements(_xpath_column)
2066
2067    def traverse_columns(  # noqa: C901
2068        self,
2069        start: int | None = None,
2070        end: int | None = None,
2071    ) -> Iterator[Column]:
2072        """Yield as many column elements as expected columns in the table,
2073        i.e. expand repetitions by returning the same column as many times as
2074        necessary.
2075
2076            Arguments:
2077
2078                start -- int
2079
2080                end -- int
2081
2082        Copies are returned, use set_column() to push them back.
2083        """
2084        idx = -1
2085        before = -1
2086        x = 0
2087        if start is None and end is None:
2088            for juska in self._cmap:
2089                idx += 1
2090                if idx in self._indexes["_cmap"]:
2091                    column = self._indexes["_cmap"][idx]
2092                else:
2093                    column = self._get_element_idx2(_xpath_column_idx, idx)
2094                    self._indexes["_cmap"][idx] = column
2095                repeated = juska - before
2096                before = juska
2097                for _i in range(repeated or 1):
2098                    # Return a copy without the now obsolete repetition
2099                    column = column.clone
2100                    column.x = x
2101                    x += 1
2102                    if repeated > 1:
2103                        column.repeated = None
2104                    yield column
2105        else:
2106            if start is None:
2107                start = 0
2108            start = max(0, start)
2109            if end is None:
2110                try:
2111                    end = self._cmap[-1]
2112                except Exception:
2113                    end = -1
2114            start_map = find_odf_idx(self._cmap, start)
2115            if start_map is None:
2116                return
2117            if start_map > 0:
2118                before = self._cmap[start_map - 1]
2119            idx = start_map - 1
2120            before = start - 1
2121            x = start
2122            for juska in self._cmap[start_map:]:
2123                idx += 1
2124                if idx in self._indexes["_cmap"]:
2125                    column = self._indexes["_cmap"][idx]
2126                else:
2127                    column = self._get_element_idx2(_xpath_column_idx, idx)
2128                    self._indexes["_cmap"][idx] = column
2129                repeated = juska - before
2130                before = juska
2131                for _i in range(repeated or 1):
2132                    if x <= end:
2133                        column = column.clone
2134                        column.x = x
2135                        x += 1
2136                        if repeated > 1 or (x == start and start > 0):
2137                            column.repeated = None
2138                        yield column
2139
2140    def get_columns(
2141        self,
2142        coord: tuple | list | str | None = None,
2143        style: str | None = None,
2144    ) -> list[Column]:
2145        """Get the list of columns matching the criteria. Each result is a
2146        tuple of (x, column).
2147
2148        Arguments:
2149
2150            coord -- str or tuple of int : coordinates of columns
2151
2152            style -- str
2153
2154        Return: list of columns
2155        """
2156        if coord:
2157            x, _y, _z, t = self._translate_column_coordinates(coord)
2158        else:
2159            x = t = None
2160        if not style:
2161            return list(self.traverse_columns(start=x, end=t))
2162        columns = []
2163        for column in self.traverse_columns(start=x, end=t):
2164            if style != column.style:
2165                continue
2166            columns.append(column)
2167        return columns
2168
2169    def _get_column2(self, x: int) -> Column | None:
2170        # Outside the defined table
2171        if x >= self.width:
2172            return Column()
2173        # Inside the defined table
2174        odf_idx = find_odf_idx(self._cmap, x)
2175        if odf_idx is not None:
2176            column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2177            if column is None:
2178                return None
2179            # fixme : no clone here => change doc and unit tests
2180            return column.clone  # type: ignore
2181            # return row
2182        return None
2183
2184    def get_column(self, x: int | str) -> Column:
2185        """Get the column at the given "x" position.
2186
2187        ODF columns don't contain cells, only style information.
2188
2189        Position start at 0. So cell C4 is on column 2. Alphabetical position
2190        like "C" is accepted.
2191
2192        A copy is returned, use set_column() to push it back.
2193
2194        Arguments:
2195
2196            x -- int or str
2197
2198        Return: Column
2199        """
2200        x = self._translate_x_from_any(x)
2201        column = self._get_column2(x)
2202        if column is None:
2203            raise ValueError
2204        column.x = x
2205        return column
2206
2207    def set_column(
2208        self,
2209        x: int | str,
2210        column: Column | None = None,
2211    ) -> Column:
2212        """Replace the column at the given "x" position.
2213
2214        ODF columns don't contain cells, only style information.
2215
2216        Position start at 0. So cell C4 is on column 2. Alphabetical position
2217        like "C" is accepted.
2218
2219        Arguments:
2220
2221            x -- int or str
2222
2223            column -- Column
2224        """
2225        x = self._translate_x_from_any(x)
2226        if column is None:
2227            column = Column()
2228            repeated = 1
2229        else:
2230            repeated = column.repeated or 1
2231        column.x = x
2232        # Outside the defined table ?
2233        diff = x - self.width
2234        if diff == 0:
2235            column_back = self.append_column(column, _repeated=repeated)
2236        elif diff > 0:
2237            self.append_column(Column(repeated=diff), _repeated=diff)
2238            column_back = self.append_column(column, _repeated=repeated)
2239        else:
2240            # Inside the defined table
2241            column_back = set_item_in_vault(  # type: ignore
2242                x, column, self, _xpath_column_idx, "_cmap"
2243            )
2244        return column_back
2245
2246    def insert_column(
2247        self,
2248        x: int | str,
2249        column: Column | None = None,
2250    ) -> Column:
2251        """Insert the column before the given "x" position. If no column is
2252        given, an empty one is created.
2253
2254        ODF columns don't contain cells, only style information.
2255
2256        Position start at 0. So cell C4 is on column 2. Alphabetical position
2257        like "C" is accepted.
2258
2259        Arguments:
2260
2261            x -- int or str
2262
2263            column -- Column
2264        """
2265        if column is None:
2266            column = Column()
2267        x = self._translate_x_from_any(x)
2268        diff = x - self.width
2269        if diff < 0:
2270            column_back = insert_item_in_vault(
2271                x, column, self, _xpath_column_idx, "_cmap"
2272            )
2273        elif diff == 0:
2274            column_back = self.append_column(column.clone)
2275        else:
2276            self.append_column(Column(repeated=diff), _repeated=diff)
2277            column_back = self.append_column(column.clone)
2278        column_back.x = x  # type: ignore
2279        # Repetitions are accepted
2280        repeated = column.repeated or 1
2281        # Update width on every row
2282        for row in self._get_rows():
2283            if row.width > x:
2284                row.insert_cell(x, Cell(repeated=repeated))
2285            # Shorter rows don't need insert
2286            # Longer rows shouldn't exist!
2287        return column_back  # type: ignore
2288
2289    def append_column(
2290        self,
2291        column: Column | None = None,
2292        _repeated: int | None = None,
2293    ) -> Column:
2294        """Append the column at the end of the table. If no column is given,
2295        an empty one is created.
2296
2297        ODF columns don't contain cells, only style information.
2298
2299        Position start at 0. So cell C4 is on column 2. Alphabetical position
2300        like "C" is accepted.
2301
2302        Arguments:
2303
2304            column -- Column
2305        """
2306        if column is None:
2307            column = Column()
2308        else:
2309            column = column.clone
2310        if not self._cmap:
2311            position = 0
2312        else:
2313            odf_idx = len(self._cmap) - 1
2314            last_column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2315            if last_column is None:
2316                raise ValueError
2317            position = self.index(last_column) + 1
2318        column.x = self.width
2319        self.insert(column, position=position)
2320        # Repetitions are accepted
2321        if _repeated is None:
2322            _repeated = column.repeated or 1
2323        self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated)
2324        # No need to update row widths
2325        return column
2326
2327    def delete_column(self, x: int | str) -> None:
2328        """Delete the column at the given position. ODF columns don't contain
2329        cells, only style information.
2330
2331        Position start at 0. So cell C4 is on column 2. Alphabetical position
2332        like "C" is accepted.
2333
2334        Arguments:
2335
2336            x -- int or str
2337        """
2338        x = self._translate_x_from_any(x)
2339        # Outside the defined table
2340        if x >= self.width:
2341            return
2342        # Inside the defined table
2343        delete_item_in_vault(x, self, _xpath_column_idx, "_cmap")
2344        # Update width
2345        width = self.width
2346        for row in self._get_rows():
2347            if row.width >= width:
2348                row.delete_cell(x)
2349
2350    def get_column_cells(  # noqa: C901
2351        self,
2352        x: int | str,
2353        style: str | None = None,
2354        content: str | None = None,
2355        cell_type: str | None = None,
2356        complete: bool = False,
2357    ) -> list[Cell | None]:
2358        """Get the list of cells at the given position.
2359
2360        Position start at 0. So cell C4 is on column 2. Alphabetical position
2361        like "C" is accepted.
2362
2363        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2364        type, aka non empty cells.
2365
2366        If complete is True, replace missing values by None.
2367
2368        Arguments:
2369
2370            x -- int or str
2371
2372            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2373                         'currency', 'percentage' or 'all'
2374
2375            content -- str regex
2376
2377            style -- str
2378
2379            complete -- boolean
2380
2381        Return: list of Cell
2382        """
2383        x = self._translate_x_from_any(x)
2384        if cell_type:
2385            cell_type = cell_type.lower().strip()
2386        cells: list[Cell | None] = []
2387        if not style and not content and not cell_type:
2388            for row in self.traverse():
2389                cells.append(row.get_cell(x, clone=True))
2390            return cells
2391        for row in self.traverse():
2392            cell = row.get_cell(x, clone=True)
2393            if cell is None:
2394                raise ValueError
2395            # Filter the cells by cell_type
2396            if cell_type:
2397                ctype = cell.type
2398                if not ctype or not (ctype == cell_type or cell_type == "all"):
2399                    if complete:
2400                        cells.append(None)
2401                    continue
2402            # Filter the cells with the regex
2403            if content and not cell.match(content):
2404                if complete:
2405                    cells.append(None)
2406                continue
2407            # Filter the cells with the style
2408            if style and style != cell.style:
2409                if complete:
2410                    cells.append(None)
2411                continue
2412            cells.append(cell)
2413        return cells
2414
2415    def get_column_values(
2416        self,
2417        x: int | str,
2418        cell_type: str | None = None,
2419        complete: bool = True,
2420        get_type: bool = False,
2421    ) -> list[Any]:
2422        """Shortcut to get the list of Python values for the cells at the
2423        given position.
2424
2425        Position start at 0. So cell C4 is on column 2. Alphabetical position
2426        like "C" is accepted.
2427
2428        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2429        type, aka non empty cells.
2430        If cell_type and complete is True, replace missing values by None.
2431
2432        If get_type is True, returns a tuple (value, ODF type of value)
2433
2434        Arguments:
2435
2436            x -- int or str
2437
2438            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2439                         'currency', 'percentage' or 'all'
2440
2441            complete -- boolean
2442
2443            get_type -- boolean
2444
2445        Return: list of Python types
2446        """
2447        cells = self.get_column_cells(
2448            x, style=None, content=None, cell_type=cell_type, complete=complete
2449        )
2450        values: list[Any] = []
2451        for cell in cells:
2452            if cell is None:
2453                if complete:
2454                    if get_type:
2455                        values.append((None, None))
2456                    else:
2457                        values.append(None)
2458                continue
2459            if cell_type:
2460                ctype = cell.type
2461                if not ctype or not (ctype == cell_type or cell_type == "all"):
2462                    if complete:
2463                        if get_type:
2464                            values.append((None, None))
2465                        else:
2466                            values.append(None)
2467                    continue
2468            values.append(cell.get_value(get_type=get_type))
2469        return values
2470
2471    def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2472        """Shortcut to set the list of cells at the given position.
2473
2474        Position start at 0. So cell C4 is on column 2. Alphabetical position
2475        like "C" is accepted.
2476
2477        The list must have the same length than the table height.
2478
2479        Arguments:
2480
2481            x -- int or str
2482
2483            cells -- list of Cell
2484        """
2485        height = self.height
2486        if len(cells) != height:
2487            raise ValueError(f"col mismatch: {height} cells expected")
2488        cells_iterator = iter(cells)
2489        for y, row in enumerate(self.traverse()):
2490            row.set_cell(x, next(cells_iterator))
2491            self.set_row(y, row)
2492
2493    def set_column_values(
2494        self,
2495        x: int | str,
2496        values: list,
2497        cell_type: str | None = None,
2498        currency: str | None = None,
2499        style: str | None = None,
2500    ) -> None:
2501        """Shortcut to set the list of Python values of cells at the given
2502        position.
2503
2504        Position start at 0. So cell C4 is on column 2. Alphabetical position
2505        like "C" is accepted.
2506
2507        The list must have the same length than the table height.
2508
2509        Arguments:
2510
2511            x -- int or str
2512
2513            values -- list of Python types
2514
2515            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
2516                         'string' or 'time'
2517
2518            currency -- three-letter str
2519
2520            style -- str
2521        """
2522        cells = [
2523            Cell(value, cell_type=cell_type, currency=currency, style=style)
2524            for value in values
2525        ]
2526        self.set_column_cells(x, cells)
2527
2528    def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2529        """Return wether every cell in the column at "x" position has no value
2530        or the value evaluates to False (empty string), and no style.
2531
2532        Position start at 0. So cell C4 is on column 2. Alphabetical position
2533        like "C" is accepted.
2534
2535        If aggressive is True, empty cells with style are considered empty.
2536
2537        Return: bool
2538        """
2539        for cell in self.get_column_cells(x):
2540            if cell is None:
2541                continue
2542            if not cell.is_empty(aggressive=aggressive):
2543                return False
2544        return True
2545
2546    # Named Range
2547
2548    def get_named_ranges(  # type: ignore
2549        self,
2550        table_name: str | list[str] | None = None,
2551    ) -> list[NamedRange]:
2552        """Returns the list of available Name Ranges of the spreadsheet. If
2553        table_name is provided, limits the search to these tables.
2554        Beware : named ranges are stored at the body level, thus do not call
2555        this method on a cloned table.
2556
2557        Arguments:
2558
2559            table_names -- str or list of str, names of tables
2560
2561        Return : list of table_range
2562        """
2563        body = self.document_body
2564        if not body:
2565            return []
2566        all_named_ranges = body.get_named_ranges()
2567        if not table_name:
2568            return all_named_ranges  # type:ignore
2569        filter_ = []
2570        if isinstance(table_name, str):
2571            filter_.append(table_name)
2572        elif isiterable(table_name):
2573            filter_.extend(table_name)
2574        else:
2575            raise ValueError(
2576                f"table_name must be string or Iterable, not {type(table_name)}"
2577            )
2578        return [
2579            nr for nr in all_named_ranges if nr.table_name in filter_  # type:ignore
2580        ]
2581
2582    def get_named_range(self, name: str) -> NamedRange:
2583        """Returns the Name Ranges of the specified name. If
2584        table_name is provided, limits the search to these tables.
2585        Beware : named ranges are stored at the body level, thus do not call
2586        this method on a cloned table.
2587
2588        Arguments:
2589
2590            name -- str, name of the named range object
2591
2592        Return : NamedRange
2593        """
2594        body = self.document_body
2595        if not body:
2596            raise ValueError("Table is not inside a document")
2597        return body.get_named_range(name)  # type: ignore
2598
2599    def set_named_range(
2600        self,
2601        name: str,
2602        crange: str | tuple | list,
2603        table_name: str | None = None,
2604        usage: str | None = None,
2605    ) -> None:
2606        """Create a Named Range element and insert it in the document.
2607        Beware : named ranges are stored at the body level, thus do not call
2608        this method on a cloned table.
2609
2610        Arguments:
2611
2612            name -- str, name of the named range
2613
2614            crange -- str or tuple of int, cell or area coordinate
2615
2616            table_name -- str, name of the table
2617
2618            uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2619        """
2620        body = self.document_body
2621        if not body:
2622            raise ValueError("Table is not inside a document")
2623        if not name:
2624            raise ValueError("Name required.")
2625        if table_name is None:
2626            table_name = self.name
2627        named_range = NamedRange(name, crange, table_name, usage)
2628        body.append_named_range(named_range)
2629
2630    def delete_named_range(self, name: str) -> None:
2631        """Delete the Named Range of specified name from the spreadsheet.
2632        Beware : named ranges are stored at the body level, thus do not call
2633        this method on a cloned table.
2634
2635        Arguments:
2636
2637            name -- str
2638        """
2639        name = name.strip()
2640        if not name:
2641            raise ValueError("Name required.")
2642        body = self.document_body
2643        if not body:
2644            raise ValueError("Table is not inside a document.")
2645        body.delete_named_range(name)
2646
2647    #
2648    # Cell span
2649    #
2650
2651    def set_span(  # noqa: C901
2652        self,
2653        area: str | tuple | list,
2654        merge: bool = False,
2655    ) -> bool:
2656        """Create a Cell Span : span the first cell of the area on several
2657        columns and/or rows.
2658        If merge is True, replace text of the cell by the concatenation of
2659        existing text in covered cells.
2660        Beware : if merge is True, old text is changed, if merge is False
2661        (the default), old text in coverd cells is still present but not
2662        displayed by most GUI.
2663
2664        If the area defines only one cell, the set span will do nothing.
2665        It is not allowed to apply set span to an area whose one cell already
2666        belongs to previous cell span.
2667
2668        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2669        be provided as an alpha numeric value like "A1:B2' or a tuple like
2670        (0, 0, 1, 1) or (0, 0).
2671
2672        Arguments:
2673
2674            area -- str or tuple of int, cell or area coordinate
2675
2676            merge -- boolean
2677        """
2678        # get area
2679        digits = convert_coordinates(area)
2680        if len(digits) == 4:
2681            x, y, z, t = digits
2682        else:
2683            x, y = digits
2684            z, t = digits
2685        start = x, y
2686        end = z, t
2687        if start == end:
2688            # one cell : do nothing
2689            return False
2690        if x is None:
2691            raise ValueError
2692        if y is None:
2693            raise ValueError
2694        if z is None:
2695            raise ValueError
2696        if t is None:
2697            raise ValueError
2698        # check for previous span
2699        good = True
2700        # Check boundaries and empty cells : need to crate non existent cells
2701        # so don't use get_cells directly, but get_cell
2702        cells = []
2703        for yy in range(y, t + 1):
2704            row_cells = []
2705            for xx in range(x, z + 1):
2706                row_cells.append(
2707                    self.get_cell((xx, yy), clone=True, keep_repeated=False)
2708                )
2709            cells.append(row_cells)
2710        for row in cells:
2711            for cell in row:
2712                if cell._is_spanned():
2713                    good = False
2714                    break
2715            if not good:
2716                break
2717        if not good:
2718            return False
2719        # Check boundaries
2720        # if z >= self.width or t >= self.height:
2721        #    self.set_cell(coord = end)
2722        #    print area, z, t
2723        #    cells = self.get_cells((x, y, z, t))
2724        #    print cells
2725        # do it:
2726        if merge:
2727            val_list = []
2728            for row in cells:
2729                for cell in row:
2730                    if cell.is_empty(aggressive=True):
2731                        continue
2732                    val = cell.get_value()
2733                    if val is not None:
2734                        if isinstance(val, str):
2735                            val.strip()
2736                        if val != "":
2737                            val_list.append(val)
2738                        cell.clear()
2739            if val_list:
2740                if len(val_list) == 1:
2741                    cells[0][0].set_value(val_list[0])
2742                else:
2743                    value = " ".join([str(v) for v in val_list if v])
2744                    cells[0][0].set_value(value)
2745        cols = z - x + 1
2746        cells[0][0].set_attribute("table:number-columns-spanned", str(cols))
2747        rows = t - y + 1
2748        cells[0][0].set_attribute("table:number-rows-spanned", str(rows))
2749        for cell in cells[0][1:]:
2750            cell.tag = "table:covered-table-cell"
2751        for row in cells[1:]:
2752            for cell in row:
2753                cell.tag = "table:covered-table-cell"
2754        # replace cells in table
2755        self.set_cells(cells, coord=start, clone=False)
2756        return True
2757
2758    def del_span(self, area: str | tuple | list) -> bool:
2759        """Delete a Cell Span. 'area' is the cell coordiante of the upper left
2760        cell of the spanned area.
2761
2762        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2763        be provided as an alpha numeric value like "A1:B2' or a tuple like
2764        (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell
2765        is used.
2766
2767        Arguments:
2768
2769            area -- str or tuple of int, cell or area coordinate
2770        """
2771        # get area
2772        digits = convert_coordinates(area)
2773        if len(digits) == 4:
2774            x, y, _z, _t = digits
2775        else:
2776            x, y = digits
2777        if x is None:
2778            raise ValueError
2779        if y is None:
2780            raise ValueError
2781        start = x, y
2782        # check for previous span
2783        cell0 = self.get_cell(start)
2784        nb_cols = cell0.get_attribute_integer("table:number-columns-spanned")
2785        if nb_cols is None:
2786            return False
2787        nb_rows = cell0.get_attribute_integer("table:number-rows-spanned")
2788        if nb_rows is None:
2789            return False
2790        z = x + nb_cols - 1
2791        t = y + nb_rows - 1
2792        cells = self.get_cells((x, y, z, t))
2793        cells[0][0].del_attribute("table:number-columns-spanned")
2794        cells[0][0].del_attribute("table:number-rows-spanned")
2795        for cell in cells[0][1:]:
2796            cell.tag = "table:table-cell"
2797        for row in cells[1:]:
2798            for cell in row:
2799                cell.tag = "table:table-cell"
2800        # replace cells in table
2801        self.set_cells(cells, coord=start, clone=False)
2802        return True
2803
2804    # Utilities
2805
2806    def to_csv(
2807        self,
2808        path_or_file: str | Path | None = None,
2809        dialect: str = "excel",
2810    ) -> Any:
2811        """Write the table as CSV in the file.
2812
2813        If the file is a string, it is opened as a local path. Else an
2814        opened file-like is expected.
2815
2816        Arguments:
2817
2818            path_or_file -- str or file-like
2819
2820            dialect -- str, python csv.dialect, can be 'excel', 'unix'...
2821        """
2822
2823        def write_content(csv_writer: object) -> None:
2824            for values in self.iter_values():
2825                line = []
2826                for value in values:
2827                    if value is None:
2828                        value = ""
2829                    if isinstance(value, str):
2830                        value = value.strip()
2831                    line.append(value)
2832                csv_writer.writerow(line)  # type: ignore
2833
2834        out = StringIO(newline="")
2835        csv_writer = csv.writer(out, dialect=dialect)
2836        write_content(csv_writer)
2837        if path_or_file is None:
2838            return out.getvalue()
2839        path = Path(path_or_file)
2840        path.write_text(out.getvalue())
2841        return None

ODF table "table:table"

Table( name: str | None = None, width: int | None = None, height: int | None = None, protected: bool = False, protection_key: str | None = None, display: bool = True, printable: bool = True, print_ranges: list[str] | None = None, style: str | None = None, **kwargs: Any)
291    def __init__(
292        self,
293        name: str | None = None,
294        width: int | None = None,
295        height: int | None = None,
296        protected: bool = False,
297        protection_key: str | None = None,
298        display: bool = True,
299        printable: bool = True,
300        print_ranges: list[str] | None = None,
301        style: str | None = None,
302        **kwargs: Any,
303    ) -> None:
304        """Create a table element, optionally prefilled with "height" rows of
305        "width" cells each.
306
307        If the table is to be protected, a protection key must be provided,
308        i.e. a hash value of the password.
309
310        If the table must not be displayed, set "display" to False.
311
312        If the table must not be printed, set "printable" to False. The table
313        will not be printed when it is not displayed, whatever the value of
314        this argument.
315
316        Ranges of cells to print can be provided as a list of cell ranges,
317        e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g.
318        "E6:K12 P6:R12".
319
320        You can access and modify the XML tree manually, but you probably want
321        to use the API to access and alter cells. It will save you from
322        handling repetitions and the same number of cells for each row.
323
324        If you use both the table API and the XML API, you are on your own for
325        ensuiring model integrity.
326
327        Arguments:
328
329            name -- str
330
331            width -- int
332
333            height -- int
334
335            protected -- bool
336
337            protection_key -- str
338
339            display -- bool
340
341            printable -- bool
342
343            print_ranges -- list
344
345            style -- str
346        """
347        super().__init__(**kwargs)
348        self._indexes = {}
349        self._indexes["_cmap"] = {}
350        self._indexes["_tmap"] = {}
351        if self._do_init:
352            self.name = name
353            if protected:
354                self.protected = protected
355                self.set_protection_key = protection_key
356            if not display:
357                self.displayed = display
358            if not printable:
359                self.printable = printable
360            if print_ranges:
361                self.print_ranges = print_ranges
362            if style:
363                self.style = style
364            # Prefill the table
365            if width is not None or height is not None:
366                width = width or 1
367                height = height or 1
368                # Column groups for style information
369                columns = Column(repeated=width)
370                self._append(columns)
371                for _i in range(height):
372                    row = Row(width)
373                    self._append(row)
374        self._compute_table_cache()

Create a table element, optionally prefilled with "height" rows of "width" cells each.

If the table is to be protected, a protection key must be provided, i.e. a hash value of the password.

If the table must not be displayed, set "display" to False.

If the table must not be printed, set "printable" to False. The table will not be printed when it is not displayed, whatever the value of this argument.

Ranges of cells to print can be provided as a list of cell ranges, e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. "E6:K12 P6:R12".

You can access and modify the XML tree manually, but you probably want to use the API to access and alter cells. It will save you from handling repetitions and the same number of cells for each row.

If you use both the table API and the XML API, you are on your own for ensuiring model integrity.

Arguments:

name -- str

width -- int

height -- int

protected -- bool

protection_key -- str

display -- bool

printable -- bool

print_ranges -- list

style -- str
def append(self, something: Element | str) -> None:
731    def append(self, something: Element | str) -> None:
732        """Dispatch .append() call to append_row() or append_column()."""
733        if isinstance(something, Row):
734            self.append_row(something)
735        elif isinstance(something, Column):
736            self.append_column(something)
737        else:
738            # probably still an error
739            self._append(something)

Dispatch .append() call to append_row() or append_column().

height: int
741    @property
742    def height(self) -> int:
743        """Get the current height of the table.
744
745        Return: int
746        """
747        try:
748            height = self._tmap[-1] + 1
749        except Exception:
750            height = 0
751        return height

Get the current height of the table.

Return: int

width: int
753    @property
754    def width(self) -> int:
755        """Get the current width of the table, measured on columns.
756
757        Rows may have different widths, use the Table API to ensure width
758        consistency.
759
760        Return: int
761        """
762        # Columns are our reference for user expected width
763
764        try:
765            width = self._cmap[-1] + 1
766        except Exception:
767            width = 0
768
769        # columns = self._get_columns()
770        # repeated = self.xpath(
771        #        'table:table-column/@table:number-columns-repeated')
772        # unrepeated = len(columns) - len(repeated)
773        # ws = sum(int(r) for r in repeated) + unrepeated
774        # if w != ws:
775        #    print "WARNING   ws", ws, "w", w
776
777        return width

Get the current width of the table, measured on columns.

Rows may have different widths, use the Table API to ensure width consistency.

Return: int

size: tuple[int, int]
779    @property
780    def size(self) -> tuple[int, int]:
781        """Shortcut to get the current width and height of the table.
782
783        Return: (int, int)
784        """
785        return self.width, self.height

Shortcut to get the current width and height of the table.

Return: (int, int)

name: str | None
787    @property
788    def name(self) -> str | None:
789        """Get / set the name of the table."""
790        return self.get_attribute_string("table:name")

Get / set the name of the table.

protected: bool
801    @property
802    def protected(self) -> bool:
803        return bool(self.get_attribute("table:protected"))
protection_key: str | None
809    @property
810    def protection_key(self) -> str | None:
811        return self.get_attribute_string("table:protection-key")
displayed: bool
817    @property
818    def displayed(self) -> bool:
819        return bool(self.get_attribute("table:display"))
printable: bool
825    @property
826    def printable(self) -> bool:
827        printable = self.get_attribute("table:print")
828        # Default value
829        if printable is None:
830            return True
831        return bool(printable)
print_ranges: list[str]
837    @property
838    def print_ranges(self) -> list[str]:
839        ranges = self.get_attribute_string("table:print-ranges")
840        if isinstance(ranges, str):
841            return ranges.split()
842        return []
style: str | None
851    @property
852    def style(self) -> str | None:
853        """Get / set the style of the table
854
855        Return: str
856        """
857        return self.get_attribute_string("table:style-name")

Get / set the style of the table

Return: str

def get_formatted_text(self, context: dict | None = None) -> str:
863    def get_formatted_text(self, context: dict | None = None) -> str:
864        if context and context["rst_mode"]:
865            return self._get_formatted_text_rst(context)
866        return self._get_formatted_text_normal(context)

This function should return a beautiful version of the text.

def get_values( self, coord: tuple | list | str | None = None, cell_type: str | None = None, complete: bool = True, get_type: bool = False, flat: bool = False) -> list:
868    def get_values(
869        self,
870        coord: tuple | list | str | None = None,
871        cell_type: str | None = None,
872        complete: bool = True,
873        get_type: bool = False,
874        flat: bool = False,
875    ) -> list:
876        """Get a matrix of values of the table.
877
878        Filter by coordinates will parse the area defined by the coordinates.
879
880        If 'cell_type' is used and 'complete' is True (default), missing values
881        are replaced by None.
882        Filter by ' cell_type = "all" ' will retrieve cells of any
883        type, aka non empty cells.
884
885        If 'cell_type' is None, complete is always True : with no cell type
886        queried, get_values() returns None for each empty cell, the length
887        each lists is equal to the width of the table.
888
889        If get_type is True, returns tuples (value, ODF type of value), or
890        (None, None) for empty cells with complete True.
891
892        If flat is True, the methods return a single list of all the values.
893        By default, flat is False.
894
895        Arguments:
896
897            coord -- str or tuple of int : coordinates of area
898
899            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
900                         'currency', 'percentage' or 'all'
901
902            complete -- boolean
903
904            get_type -- boolean
905
906        Return: list of lists of Python types
907        """
908        if coord:
909            x, y, z, t = self._translate_table_coordinates(coord)
910        else:
911            x = y = z = t = None
912        data = []
913        for row in self.traverse(start=y, end=t):
914            if z is None:
915                width = self.width
916            else:
917                width = min(z + 1, self.width)
918            if x is not None:
919                width -= x
920            values = row.get_values(
921                (x, z),
922                cell_type=cell_type,
923                complete=complete,
924                get_type=get_type,
925            )
926            # complete row to match request width
927            if complete:
928                if get_type:
929                    values.extend([(None, None)] * (width - len(values)))
930                else:
931                    values.extend([None] * (width - len(values)))
932            if flat:
933                data.extend(values)
934            else:
935                data.append(values)
936        return data

Get a matrix of values of the table.

Filter by coordinates will parse the area defined by the coordinates.

If 'cell_type' is used and 'complete' is True (default), missing values are replaced by None. Filter by ' cell_type = "all" ' will retrieve cells of any type, aka non empty cells.

If 'cell_type' is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length each lists is equal to the width of the table.

If get_type is True, returns tuples (value, ODF type of value), or (None, None) for empty cells with complete True.

If flat is True, the methods return a single list of all the values. By default, flat is False.

Arguments:

coord -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of lists of Python types

def iter_values( self, coord: tuple | list | str | None = None, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> collections.abc.Iterator[list]:
938    def iter_values(
939        self,
940        coord: tuple | list | str | None = None,
941        cell_type: str | None = None,
942        complete: bool = True,
943        get_type: bool = False,
944    ) -> Iterator[list]:
945        """Iterate through lines of Python values of the table.
946
947        Filter by coordinates will parse the area defined by the coordinates.
948
949        cell_type, complete, grt_type : see get_values()
950
951
952
953        Arguments:
954
955            coord -- str or tuple of int : coordinates of area
956
957            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
958                         'currency', 'percentage' or 'all'
959
960            complete -- boolean
961
962            get_type -- boolean
963
964        Return: iterator of lists
965        """
966        if coord:
967            x, y, z, t = self._translate_table_coordinates(coord)
968        else:
969            x = y = z = t = None
970        for row in self.traverse(start=y, end=t):
971            if z is None:
972                width = self.width
973            else:
974                width = min(z + 1, self.width)
975            if x is not None:
976                width -= x
977            values = row.get_values(
978                (x, z),
979                cell_type=cell_type,
980                complete=complete,
981                get_type=get_type,
982            )
983            # complete row to match column width
984            if complete:
985                if get_type:
986                    values.extend([(None, None)] * (width - len(values)))
987                else:
988                    values.extend([None] * (width - len(values)))
989            yield values

Iterate through lines of Python values of the table.

Filter by coordinates will parse the area defined by the coordinates.

cell_type, complete, grt_type : see get_values()

Arguments:

coord -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: iterator of lists

def set_values( self, values: list, coord: tuple | list | str | None = None, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
 991    def set_values(
 992        self,
 993        values: list,
 994        coord: tuple | list | str | None = None,
 995        style: str | None = None,
 996        cell_type: str | None = None,
 997        currency: str | None = None,
 998    ) -> None:
 999        """Set the value of cells in the table, from the 'coord' position
1000        with values.
1001
1002        'coord' is the coordinate of the upper left cell to be modified by
1003        values. If 'coord' is None, default to the position (0,0) ("A1").
1004        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1005        area is used as coordinate.
1006
1007        The table is *not* cleared before the operation, to reset the table
1008        before setting values, use table.clear().
1009
1010        A list of lists is expected, with as many lists as rows, and as many
1011        items in each sublist as cells to be setted. None values in the list
1012        will create empty cells with no cell type (but eventually a style).
1013
1014        Arguments:
1015
1016            coord -- tuple or str
1017
1018            values -- list of lists of python types
1019
1020            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1021                         'string' or 'time'
1022
1023            currency -- three-letter str
1024
1025            style -- str
1026        """
1027        if coord:
1028            x, y = self._translate_cell_coordinates(coord)
1029        else:
1030            x = y = 0
1031        if y is None:
1032            y = 0
1033        if x is None:
1034            x = 0
1035        y -= 1
1036        for row_values in values:
1037            y += 1
1038            if not row_values:
1039                continue
1040            row = self.get_row(y, clone=True)
1041            repeated = row.repeated or 1
1042            if repeated >= 2:
1043                row.repeated = None
1044            row.set_values(
1045                row_values,
1046                start=x,
1047                cell_type=cell_type,
1048                currency=currency,
1049                style=style,
1050            )
1051            self.set_row(y, row, clone=False)
1052            self._update_width(row)

Set the value of cells in the table, from the 'coord' position with values.

'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.

The table is not cleared before the operation, to reset the table before setting values, use table.clear().

A list of lists is expected, with as many lists as rows, and as many items in each sublist as cells to be setted. None values in the list will create empty cells with no cell type (but eventually a style).

Arguments:

coord -- tuple or str

values -- list of lists of python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str
def rstrip(self, aggressive: bool = False) -> None:
1054    def rstrip(self, aggressive: bool = False) -> None:
1055        """Remove *in-place* empty rows below and empty cells at the right of
1056        the table. Cells are empty if they contain no value or it evaluates
1057        to False, and no style.
1058
1059        If aggressive is True, empty cells with style are removed too.
1060
1061        Argument:
1062
1063            aggressive -- bool
1064        """
1065        # Step 1: remove empty rows below the table
1066        for row in reversed(self._get_rows()):
1067            if row.is_empty(aggressive=aggressive):
1068                row.parent.delete(row)  # type: ignore
1069            else:
1070                break
1071        # Step 2: rstrip remaining rows
1072        max_width = 0
1073        for row in self._get_rows():
1074            row.rstrip(aggressive=aggressive)
1075            # keep count of the biggest row
1076            max_width = max(max_width, row.width)
1077        # raz cache of rows
1078        self._indexes["_tmap"] = {}
1079        # Step 3: trim columns to match max_width
1080        columns = self._get_columns()
1081        repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated")
1082        if not isinstance(repeated_cols, list):
1083            raise TypeError
1084        unrepeated = len(columns) - len(repeated_cols)
1085        column_width = sum(int(r) for r in repeated_cols) + unrepeated  # type: ignore
1086        diff = column_width - max_width
1087        if diff > 0:
1088            for column in reversed(columns):
1089                repeated = column.repeated or 1
1090                repeated = repeated - diff
1091                if repeated > 0:
1092                    column.repeated = repeated
1093                    break
1094                else:
1095                    column.parent.delete(column)
1096                    diff = -repeated
1097                    if diff == 0:
1098                        break
1099        # raz cache of columns
1100        self._indexes["_cmap"] = {}
1101        self._compute_table_cache()

Remove in-place empty rows below and empty cells at the right of the table. Cells are empty if they contain no value or it evaluates to False, and no style.

If aggressive is True, empty cells with style are removed too.

Argument:

aggressive -- bool
def transpose(self, coord: tuple | list | str | None = None) -> None:
1103    def transpose(self, coord: tuple | list | str | None = None) -> None:  # noqa: C901
1104        """Swap *in-place* rows and columns of the table.
1105
1106        If 'coord' is not None, apply transpose only to the area defined by the
1107        coordinates. Beware, if area is not square, some cells mays be over
1108        written during the process.
1109
1110        Arguments:
1111
1112            coord -- str or tuple of int : coordinates of area
1113
1114            start -- int or str
1115        """
1116        data = []
1117        if coord is None:
1118            for row in self.traverse():
1119                data.append(list(row.traverse()))
1120            transposed_data = zip_longest(*data)
1121            self.clear()
1122            # new_rows = []
1123            for row_cells in transposed_data:
1124                if not isiterable(row_cells):
1125                    row_cells = (row_cells,)
1126                row = Row()
1127                row.extend_cells(row_cells)
1128                self.append_row(row, clone=False)
1129            self._compute_table_cache()
1130        else:
1131            x, y, z, t = self._translate_table_coordinates(coord)
1132            if x is None:
1133                x = 0
1134            else:
1135                x = min(x, self.width - 1)
1136            if z is None:
1137                z = self.width - 1
1138            else:
1139                z = min(z, self.width - 1)
1140            if y is None:
1141                y = 0
1142            else:
1143                y = min(y, self.height - 1)
1144            if t is None:
1145                t = self.height - 1
1146            else:
1147                t = min(t, self.height - 1)
1148            for row in self.traverse(start=y, end=t):
1149                data.append(list(row.traverse(start=x, end=z)))
1150            transposed_data = zip_longest(*data)
1151            # clear locally
1152            w = z - x + 1
1153            h = t - y + 1
1154            if w != h:
1155                nones = [[None] * w for i in range(h)]
1156                self.set_values(nones, coord=(x, y, z, t))
1157            # put transposed
1158            filtered_data: list[tuple[Cell]] = []
1159            for row_cells in transposed_data:
1160                if isinstance(row_cells, (list, tuple)):
1161                    filtered_data.append(row_cells)
1162                else:
1163                    filtered_data.append((row_cells,))
1164            self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1))
1165            self._compute_table_cache()

Swap in-place rows and columns of the table.

If 'coord' is not None, apply transpose only to the area defined by the coordinates. Beware, if area is not square, some cells mays be over written during the process.

Arguments:

coord -- str or tuple of int : coordinates of area

start -- int or str
def is_empty(self, aggressive: bool = False) -> bool:
1167    def is_empty(self, aggressive: bool = False) -> bool:
1168        """Return whether every cell in the table has no value or the value
1169        evaluates to False (empty string), and no style.
1170
1171        If aggressive is True, empty cells with style are considered empty.
1172
1173        Arguments:
1174
1175            aggressive -- bool
1176        """
1177        return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())

Return whether every cell in the table has no value or the value evaluates to False (empty string), and no style.

If aggressive is True, empty cells with style are considered empty.

Arguments:

aggressive -- bool
def traverse( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Row]:
1186    def traverse(  # noqa: C901
1187        self,
1188        start: int | None = None,
1189        end: int | None = None,
1190    ) -> Iterator[Row]:
1191        """Yield as many row elements as expected rows in the table, i.e.
1192        expand repetitions by returning the same row as many times as
1193        necessary.
1194
1195            Arguments:
1196
1197                start -- int
1198
1199                end -- int
1200
1201        Copies are returned, use set_row() to push them back.
1202        """
1203        idx = -1
1204        before = -1
1205        y = 0
1206        if start is None and end is None:
1207            for juska in self._tmap:
1208                idx += 1
1209                if idx in self._indexes["_tmap"]:
1210                    row = self._indexes["_tmap"][idx]
1211                else:
1212                    row = self._get_element_idx2(_xpath_row_idx, idx)
1213                    self._indexes["_tmap"][idx] = row
1214                repeated = juska - before
1215                before = juska
1216                for _i in range(repeated or 1):
1217                    # Return a copy without the now obsolete repetition
1218                    row = row.clone
1219                    row.y = y
1220                    y += 1
1221                    if repeated > 1:
1222                        row.repeated = None
1223                    yield row
1224        else:
1225            if start is None:
1226                start = 0
1227            start = max(0, start)
1228            if end is None:
1229                try:
1230                    end = self._tmap[-1]
1231                except Exception:
1232                    end = -1
1233            start_map = find_odf_idx(self._tmap, start)
1234            if start_map is None:
1235                return
1236            if start_map > 0:
1237                before = self._tmap[start_map - 1]
1238            idx = start_map - 1
1239            before = start - 1
1240            y = start
1241            for juska in self._tmap[start_map:]:
1242                idx += 1
1243                if idx in self._indexes["_tmap"]:
1244                    row = self._indexes["_tmap"][idx]
1245                else:
1246                    row = self._get_element_idx2(_xpath_row_idx, idx)
1247                    self._indexes["_tmap"][idx] = row
1248                repeated = juska - before
1249                before = juska
1250                for _i in range(repeated or 1):
1251                    if y <= end:
1252                        row = row.clone
1253                        row.y = y
1254                        y += 1
1255                        if repeated > 1 or (y == start and start > 0):
1256                            row.repeated = None
1257                        yield row

Yield as many row elements as expected rows in the table, i.e. expand repetitions by returning the same row as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_row() to push them back.

def get_rows( self, coord: tuple | list | str | None = None, style: str | None = None, content: str | None = None) -> list[Row]:
1259    def get_rows(
1260        self,
1261        coord: tuple | list | str | None = None,
1262        style: str | None = None,
1263        content: str | None = None,
1264    ) -> list[Row]:
1265        """Get the list of rows matching the criteria.
1266
1267        Filter by coordinates will parse the area defined by the coordinates.
1268
1269        Arguments:
1270
1271            coord -- str or tuple of int : coordinates of rows
1272
1273            content -- str regex
1274
1275            style -- str
1276
1277        Return: list of rows
1278        """
1279        if coord:
1280            _x, y, _z, t = self._translate_table_coordinates(coord)
1281        else:
1282            y = t = None
1283        # fixme : not clones ?
1284        if not content and not style:
1285            return list(self.traverse(start=y, end=t))
1286        rows = []
1287        for row in self.traverse(start=y, end=t):
1288            if content and not row.match(content):
1289                continue
1290            if style and style != row.style:
1291                continue
1292            rows.append(row)
1293        return rows

Get the list of rows matching the criteria.

Filter by coordinates will parse the area defined by the coordinates.

Arguments:

coord -- str or tuple of int : coordinates of rows

content -- str regex

style -- str

Return: list of rows

def get_row( self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1318    def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1319        """Get the row at the given "y" position.
1320
1321        Position start at 0. So cell A4 is on row 3.
1322
1323        A copy is returned, use set_cell() to push it back.
1324
1325        Arguments:
1326
1327            y -- int or str
1328
1329        Return: Row
1330        """
1331        # fixme : keep repeat ? maybe an option to functions : "raw=False"
1332        y = self._translate_y_from_any(y)
1333        row = self._get_row2(y, clone=clone, create=create)
1334        if row is None:
1335            raise ValueError("Row not found")
1336        row.y = y
1337        return row

Get the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

A copy is returned, use set_cell() to push it back.

Arguments:

y -- int or str

Return: Row

def set_row( self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1339    def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1340        """Replace the row at the given position with the new one. Repetions of
1341        the old row will be adjusted.
1342
1343        If row is None, a new empty row is created.
1344
1345        Position start at 0. So cell A4 is on row 3.
1346
1347        Arguments:
1348
1349            y -- int or str
1350
1351            row -- Row
1352
1353        returns the row, with updated row.y
1354        """
1355        if row is None:
1356            row = Row()
1357            repeated = 1
1358            clone = False
1359        else:
1360            repeated = row.repeated or 1
1361        y = self._translate_y_from_any(y)
1362        row.y = y
1363        # Outside the defined table ?
1364        diff = y - self.height
1365        if diff == 0:
1366            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1367        elif diff > 0:
1368            self.append_row(Row(repeated=diff), _repeated=diff, clone=clone)
1369            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1370        else:
1371            # Inside the defined table
1372            row_back = set_item_in_vault(  # type: ignore
1373                y, row, self, _xpath_row_idx, "_tmap", clone=clone
1374            )
1375        # print self.serialize(True)
1376        # Update width if necessary
1377        self._update_width(row_back)
1378        return row_back

Replace the row at the given position with the new one. Repetions of the old row will be adjusted.

If row is None, a new empty row is created.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

row -- Row

returns the row, with updated row.y

def insert_row( self, y: str | int, row: Row | None = None, clone: bool = True) -> Row:
1380    def insert_row(
1381        self, y: str | int, row: Row | None = None, clone: bool = True
1382    ) -> Row:
1383        """Insert the row before the given "y" position. If no row is given,
1384        an empty one is created.
1385
1386        Position start at 0. So cell A4 is on row 3.
1387
1388        If row is None, a new empty row is created.
1389
1390        Arguments:
1391
1392            y -- int or str
1393
1394            row -- Row
1395
1396        returns the row, with updated row.y
1397        """
1398        if row is None:
1399            row = Row()
1400            clone = False
1401        y = self._translate_y_from_any(y)
1402        diff = y - self.height
1403        if diff < 0:
1404            row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap")
1405        elif diff == 0:
1406            row_back = self.append_row(row, clone=clone)
1407        else:
1408            self.append_row(Row(repeated=diff), _repeated=diff, clone=False)
1409            row_back = self.append_row(row, clone=clone)
1410        row_back.y = y  # type: ignore
1411        # Update width if necessary
1412        self._update_width(row_back)  # type: ignore
1413        return row_back  # type: ignore

Insert the row before the given "y" position. If no row is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

If row is None, a new empty row is created.

Arguments:

y -- int or str

row -- Row

returns the row, with updated row.y

def extend_rows(self, rows: list[Row] | None = None) -> None:
1415    def extend_rows(self, rows: list[Row] | None = None) -> None:
1416        """Append a list of rows at the end of the table.
1417
1418        Arguments:
1419
1420            rows -- list of Row
1421        """
1422        if rows is None:
1423            rows = []
1424        self.extend(rows)
1425        self._compute_table_cache()
1426        # Update width if necessary
1427        width = self.width
1428        for row in self.traverse():
1429            if row.width > width:
1430                width = row.width
1431        diff = width - self.width
1432        if diff > 0:
1433            self.append_column(Column(repeated=diff))

Append a list of rows at the end of the table.

Arguments:

rows -- list of Row
def append_row( self, row: Row | None = None, clone: bool = True, _repeated: int | None = None) -> Row:
1435    def append_row(
1436        self,
1437        row: Row | None = None,
1438        clone: bool = True,
1439        _repeated: int | None = None,
1440    ) -> Row:
1441        """Append the row at the end of the table. If no row is given, an
1442        empty one is created.
1443
1444        Position start at 0. So cell A4 is on row 3.
1445
1446        Note the columns are automatically created when the first row is
1447        inserted in an empty table. So better insert a filled row.
1448
1449        Arguments:
1450
1451            row -- Row
1452
1453            _repeated -- (optional), repeated value of the row
1454
1455        returns the row, with updated row.y
1456        """
1457        if row is None:
1458            row = Row()
1459            _repeated = 1
1460        elif clone:
1461            row = row.clone
1462        # Appending a repeated row accepted
1463        # Do not insert next to the last row because it could be in a group
1464        self._append(row)
1465        if _repeated is None:
1466            _repeated = row.repeated or 1
1467        self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated)
1468        row.y = self.height - 1
1469        # Initialize columns
1470        if not self._get_columns():
1471            repeated = row.width
1472            self.insert(Column(repeated=repeated), position=0)
1473            self._compute_table_cache()
1474        # Update width if necessary
1475        self._update_width(row)
1476        return row

Append the row at the end of the table. If no row is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

Note the columns are automatically created when the first row is inserted in an empty table. So better insert a filled row.

Arguments:

row -- Row

_repeated -- (optional), repeated value of the row

returns the row, with updated row.y

def delete_row(self, y: int | str) -> None:
1478    def delete_row(self, y: int | str) -> None:
1479        """Delete the row at the given "y" position.
1480
1481        Position start at 0. So cell A4 is on row 3.
1482
1483        Arguments:
1484
1485            y -- int or str
1486        """
1487        y = self._translate_y_from_any(y)
1488        # Outside the defined table
1489        if y >= self.height:
1490            return
1491        # Inside the defined table
1492        delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")

Delete the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str
def get_row_values( self, y: int | str, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> list:
1494    def get_row_values(
1495        self,
1496        y: int | str,
1497        cell_type: str | None = None,
1498        complete: bool = True,
1499        get_type: bool = False,
1500    ) -> list:
1501        """Shortcut to get the list of Python values for the cells of the row
1502        at the given "y" position.
1503
1504        Position start at 0. So cell A4 is on row 3.
1505
1506        Filter by cell_type, with cell_type 'all' will retrieve cells of any
1507        type, aka non empty cells.
1508        If cell_type and complete is True, replace missing values by None.
1509
1510        If get_type is True, returns a tuple (value, ODF type of value)
1511
1512        Arguments:
1513
1514            y -- int, str
1515
1516            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1517                         'currency', 'percentage' or 'all'
1518
1519            complete -- boolean
1520
1521            get_type -- boolean
1522
1523        Return: list of lists of Python types
1524        """
1525        values = self.get_row(y, clone=False).get_values(
1526            cell_type=cell_type, complete=complete, get_type=get_type
1527        )
1528        # complete row to match column width
1529        if complete:
1530            if get_type:
1531                values.extend([(None, None)] * (self.width - len(values)))
1532            else:
1533                values.extend([None] * (self.width - len(values)))
1534        return values

Shortcut to get the list of Python values for the cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.

If get_type is True, returns a tuple (value, ODF type of value)

Arguments:

y -- int, str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of lists of Python types

def set_row_values( self, y: int | str, values: list, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> Row:
1536    def set_row_values(
1537        self,
1538        y: int | str,
1539        values: list,
1540        cell_type: str | None = None,
1541        currency: str | None = None,
1542        style: str | None = None,
1543    ) -> Row:
1544        """Shortcut to set the values of *all* cells of the row at the given
1545        "y" position.
1546
1547        Position start at 0. So cell A4 is on row 3.
1548
1549        Arguments:
1550
1551            y -- int or str
1552
1553            values -- list of Python types
1554
1555            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1556                         'string' or 'time'
1557
1558            currency -- three-letter str
1559
1560            style -- str
1561
1562        returns the row, with updated row.y
1563        """
1564        row = Row()  # needed if clones rows
1565        row.set_values(values, style=style, cell_type=cell_type, currency=currency)
1566        return self.set_row(y, row)  # needed if clones rows

Shortcut to set the values of all cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

values -- list of Python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str

returns the row, with updated row.y

def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1568    def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1569        """Shortcut to set *all* the cells of the row at the given
1570        "y" position.
1571
1572        Position start at 0. So cell A4 is on row 3.
1573
1574        Arguments:
1575
1576            y -- int or str
1577
1578            cells -- list of Python types
1579
1580            style -- str
1581
1582        returns the row, with updated row.y
1583        """
1584        if cells is None:
1585            cells = []
1586        row = Row()  # needed if clones rows
1587        row.extend_cells(cells)
1588        return self.set_row(y, row)  # needed if clones rows

Shortcut to set all the cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

cells -- list of Python types

style -- str

returns the row, with updated row.y

def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1590    def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1591        """Return wether every cell in the row at the given "y" position has
1592        no value or the value evaluates to False (empty string), and no style.
1593
1594        Position start at 0. So cell A4 is on row 3.
1595
1596        If aggressive is True, empty cells with style are considered empty.
1597
1598        Arguments:
1599
1600            y -- int or str
1601
1602            aggressive -- bool
1603        """
1604        return self.get_row(y, clone=False).is_empty(aggressive=aggressive)

Return wether every cell in the row at the given "y" position has no value or the value evaluates to False (empty string), and no style.

Position start at 0. So cell A4 is on row 3.

If aggressive is True, empty cells with style are considered empty.

Arguments:

y -- int or str

aggressive -- bool
def get_cells( self, coord: tuple | list | str | None = None, cell_type: str | None = None, style: str | None = None, content: str | None = None, flat: bool = False) -> list:
1610    def get_cells(
1611        self,
1612        coord: tuple | list | str | None = None,
1613        cell_type: str | None = None,
1614        style: str | None = None,
1615        content: str | None = None,
1616        flat: bool = False,
1617    ) -> list:
1618        """Get the cells matching the criteria. If 'coord' is None,
1619        parse the whole table, else parse the area defined by 'coord'.
1620
1621        Filter by  cell_type = "all"  will retrieve cells of any
1622        type, aka non empty cells.
1623
1624        If flat is True (default is False), the method return a single list
1625        of all the values, else a list of lists of cells.
1626
1627        if cell_type, style and content are None, get_cells() will return
1628        the exact number of cells of the area, including empty cells.
1629
1630        Arguments:
1631
1632            coordinates -- str or tuple of int : coordinates of area
1633
1634            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1635                         'currency', 'percentage' or 'all'
1636
1637            content -- str regex
1638
1639            style -- str
1640
1641            flat -- boolean
1642
1643        Return: list of tuples
1644        """
1645        if coord:
1646            x, y, z, t = self._translate_table_coordinates(coord)
1647        else:
1648            x = y = z = t = None
1649        if flat:
1650            cells: list[Cell] = []
1651            for row in self.traverse(start=y, end=t):
1652                row_cells = row.get_cells(
1653                    coord=(x, z),
1654                    cell_type=cell_type,
1655                    style=style,
1656                    content=content,
1657                )
1658                cells.extend(row_cells)
1659            return cells
1660        else:
1661            lcells: list[list[Cell]] = []
1662            for row in self.traverse(start=y, end=t):
1663                row_cells = row.get_cells(
1664                    coord=(x, z),
1665                    cell_type=cell_type,
1666                    style=style,
1667                    content=content,
1668                )
1669                lcells.append(row_cells)
1670            return lcells

Get the cells matching the criteria. If 'coord' is None, parse the whole table, else parse the area defined by 'coord'.

Filter by cell_type = "all" will retrieve cells of any type, aka non empty cells.

If flat is True (default is False), the method return a single list of all the values, else a list of lists of cells.

if cell_type, style and content are None, get_cells() will return the exact number of cells of the area, including empty cells.

Arguments:

coordinates -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

flat -- boolean

Return: list of tuples

def get_cell( self, coord: tuple | list | str, clone: bool = True, keep_repeated: bool = True) -> Cell:
1672    def get_cell(
1673        self,
1674        coord: tuple | list | str,
1675        clone: bool = True,
1676        keep_repeated: bool = True,
1677    ) -> Cell:
1678        """Get the cell at the given coordinates.
1679
1680        They are either a 2-uplet of (x, y) starting from 0, or a
1681        human-readable position like "C4".
1682
1683        A copy is returned, use ``set_cell`` to push it back.
1684
1685        Arguments:
1686
1687            coord -- (int, int) or str
1688
1689        Return: Cell
1690        """
1691        x, y = self._translate_cell_coordinates(coord)
1692        if x is None:
1693            raise ValueError
1694        if y is None:
1695            raise ValueError
1696        # Outside the defined table
1697        if y >= self.height:
1698            cell = Cell()
1699        else:
1700            # Inside the defined table
1701            row = self._get_row2_base(y)
1702            if row is None:
1703                raise ValueError
1704            read_cell = row.get_cell(x, clone=clone)
1705            if read_cell is None:
1706                raise ValueError
1707            cell = read_cell
1708            if not keep_repeated:
1709                repeated = cell.repeated or 1
1710                if repeated >= 2:
1711                    cell.repeated = None
1712        cell.x = x
1713        cell.y = y
1714        return cell

Get the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

A copy is returned, use set_cell to push it back.

Arguments:

coord -- (int, int) or str

Return: Cell

def get_value(self, coord: tuple | list | str, get_type: bool = False) -> Any:
1716    def get_value(
1717        self,
1718        coord: tuple | list | str,
1719        get_type: bool = False,
1720    ) -> Any:
1721        """Shortcut to get the Python value of the cell at the given
1722        coordinates.
1723
1724        If get_type is True, returns the tuples (value, ODF type)
1725
1726        coord is either a 2-uplet of (x, y) starting from 0, or a
1727        human-readable position like "C4". If an Area is given, the upper
1728        left position is used as coord.
1729
1730        Arguments:
1731
1732            coord -- (int, int) or str : coordinate
1733
1734        Return: Python type
1735        """
1736        x, y = self._translate_cell_coordinates(coord)
1737        if x is None:
1738            raise ValueError
1739        if y is None:
1740            raise ValueError
1741        # Outside the defined table
1742        if y >= self.height:
1743            if get_type:
1744                return (None, None)
1745            return None
1746        else:
1747            # Inside the defined table
1748            row = self._get_row2_base(y)
1749            if row is None:
1750                raise ValueError
1751            cell = row._get_cell2_base(x)
1752            if cell is None:
1753                if get_type:
1754                    return (None, None)
1755                return None
1756            return cell.get_value(get_type=get_type)

Shortcut to get the Python value of the cell at the given coordinates.

If get_type is True, returns the tuples (value, ODF type)

coord is either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4". If an Area is given, the upper left position is used as coord.

Arguments:

coord -- (int, int) or str : coordinate

Return: Python type

def set_cell( self, coord: tuple | list | str, cell: Cell | None = None, clone: bool = True) -> Cell:
1758    def set_cell(
1759        self,
1760        coord: tuple | list | str,
1761        cell: Cell | None = None,
1762        clone: bool = True,
1763    ) -> Cell:
1764        """Replace a cell of the table at the given coordinates.
1765
1766        They are either a 2-uplet of (x, y) starting from 0, or a
1767        human-readable position like "C4".
1768
1769        Arguments:
1770
1771            coord -- (int, int) or str : coordinate
1772
1773            cell -- Cell
1774
1775        return the cell, with x and y updated
1776        """
1777        if cell is None:
1778            cell = Cell()
1779            clone = False
1780        x, y = self._translate_cell_coordinates(coord)
1781        if x is None:
1782            raise ValueError
1783        if y is None:
1784            raise ValueError
1785        cell.x = x
1786        cell.y = y
1787        if y >= self.height:
1788            row = Row()
1789            cell_back = row.set_cell(x, cell, clone=clone)
1790            self.set_row(y, row, clone=False)
1791        else:
1792            row_read = self._get_row2_base(y)
1793            if row_read is None:
1794                raise ValueError
1795            row = row_read
1796            row.y = y
1797            repeated = row.repeated or 1
1798            if repeated > 1:
1799                row = row.clone
1800                row.repeated = None
1801                cell_back = row.set_cell(x, cell, clone=clone)
1802                self.set_row(y, row, clone=False)
1803            else:
1804                cell_back = row.set_cell(x, cell, clone=clone)
1805                # Update width if necessary, since we don't use set_row
1806                self._update_width(row)
1807        return cell_back

Replace a cell of the table at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Arguments:

coord -- (int, int) or str : coordinate

cell -- Cell

return the cell, with x and y updated

def set_cells( self, cells: list[list[Cell]] | list[tuple[Cell]], coord: tuple | list | str | None = None, clone: bool = True) -> None:
1809    def set_cells(
1810        self,
1811        cells: list[list[Cell]] | list[tuple[Cell]],
1812        coord: tuple | list | str | None = None,
1813        clone: bool = True,
1814    ) -> None:
1815        """Set the cells in the table, from the 'coord' position.
1816
1817        'coord' is the coordinate of the upper left cell to be modified by
1818        values. If 'coord' is None, default to the position (0,0) ("A1").
1819        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1820        area is used as coordinate.
1821
1822        The table is *not* cleared before the operation, to reset the table
1823        before setting cells, use table.clear().
1824
1825        A list of lists is expected, with as many lists as rows to be set, and
1826        as many cell in each sublist as cells to be setted in the row.
1827
1828        Arguments:
1829
1830            cells -- list of list of cells
1831
1832            coord -- tuple or str
1833
1834            values -- list of lists of python types
1835        """
1836        if coord:
1837            x, y = self._translate_cell_coordinates(coord)
1838        else:
1839            x = y = 0
1840        if y is None:
1841            y = 0
1842        if x is None:
1843            x = 0
1844        y -= 1
1845        for row_cells in cells:
1846            y += 1
1847            if not row_cells:
1848                continue
1849            row = self.get_row(y, clone=True)
1850            repeated = row.repeated or 1
1851            if repeated >= 2:
1852                row.repeated = None
1853            row.set_cells(row_cells, start=x, clone=clone)
1854            self.set_row(y, row, clone=False)
1855            self._update_width(row)

Set the cells in the table, from the 'coord' position.

'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.

The table is not cleared before the operation, to reset the table before setting cells, use table.clear().

A list of lists is expected, with as many lists as rows to be set, and as many cell in each sublist as cells to be setted in the row.

Arguments:

cells -- list of list of cells

coord -- tuple or str

values -- list of lists of python types
def set_value( self, coord: tuple | list | str, value: Any, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
1857    def set_value(
1858        self,
1859        coord: tuple | list | str,
1860        value: Any,
1861        cell_type: str | None = None,
1862        currency: str | None = None,
1863        style: str | None = None,
1864    ) -> None:
1865        """Set the Python value of the cell at the given coordinates.
1866
1867        They are either a 2-uplet of (x, y) starting from 0, or a
1868        human-readable position like "C4".
1869
1870        Arguments:
1871
1872            coord -- (int, int) or str
1873
1874            value -- Python type
1875
1876            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1877                     'string' or 'time'
1878
1879            currency -- three-letter str
1880
1881            style -- str
1882
1883        """
1884        self.set_cell(
1885            coord,
1886            Cell(value, cell_type=cell_type, currency=currency, style=style),
1887            clone=False,
1888        )

Set the Python value of the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Arguments:

coord -- (int, int) or str

value -- Python type

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
         'string' or 'time'

currency -- three-letter str

style -- str
def set_cell_image( self, coord: tuple | list | str, image_frame: Frame, doc_type: str | None = None) -> None:
1890    def set_cell_image(
1891        self,
1892        coord: tuple | list | str,
1893        image_frame: Frame,
1894        doc_type: str | None = None,
1895    ) -> None:
1896        """Do all the magic to display an image in the cell at the given
1897        coordinates.
1898
1899        They are either a 2-uplet of (x, y) starting from 0, or a
1900        human-readable position like "C4".
1901
1902        The frame element must contain the expected image position and
1903        dimensions.
1904
1905        DrawImage insertion depends on the document type, so the type must be
1906        provided or the table element must be already attached to a document.
1907
1908        Arguments:
1909
1910            coord -- (int, int) or str
1911
1912            image_frame -- Frame including an image
1913
1914            doc_type -- 'spreadsheet' or 'text'
1915        """
1916        # Test document type
1917        if doc_type is None:
1918            body = self.document_body
1919            if body is None:
1920                raise ValueError("document type not found")
1921            doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get(
1922                body.tag
1923            )
1924            if doc_type is None:
1925                raise ValueError("document type not supported for images")
1926        # We need the end address of the image
1927        x, y = self._translate_cell_coordinates(coord)
1928        if x is None:
1929            raise ValueError
1930        if y is None:
1931            raise ValueError
1932        cell = self.get_cell((x, y))
1933        image_frame = image_frame.clone  # type: ignore
1934        # Remove any previous paragraph, frame, etc.
1935        for child in cell.children:
1936            cell.delete(child)
1937        # Now it all depends on the document type
1938        if doc_type == "spreadsheet":
1939            image_frame.anchor_type = "char"
1940            # The frame needs end coordinates
1941            width, height = image_frame.size
1942            image_frame.set_attribute("table:end-x", width)
1943            image_frame.set_attribute("table:end-y", height)
1944            # FIXME what happens when the address changes?
1945            address = f"{self.name}.{digit_to_alpha(x)}{y + 1}"
1946            image_frame.set_attribute("table:end-cell-address", address)
1947            # The frame is directly in the cell
1948            cell.append(image_frame)
1949        elif doc_type == "text":
1950            # The frame must be in a paragraph
1951            cell.set_value("")
1952            paragraph = cell.get_element("text:p")
1953            if paragraph is None:
1954                raise ValueError
1955            paragraph.append(image_frame)
1956        self.set_cell(coord, cell)

Do all the magic to display an image in the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

The frame element must contain the expected image position and dimensions.

DrawImage insertion depends on the document type, so the type must be provided or the table element must be already attached to a document.

Arguments:

coord -- (int, int) or str

image_frame -- Frame including an image

doc_type -- 'spreadsheet' or 'text'
def insert_cell( self, coord: tuple | list | str, cell: Cell | None = None, clone: bool = True) -> Cell:
1958    def insert_cell(
1959        self,
1960        coord: tuple | list | str,
1961        cell: Cell | None = None,
1962        clone: bool = True,
1963    ) -> Cell:
1964        """Insert the given cell at the given coordinates. If no cell is
1965        given, an empty one is created.
1966
1967        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
1968        human-readable position like "C4".
1969
1970        Cells on the right are shifted. Other rows remain untouched.
1971
1972        Arguments:
1973
1974            coord -- (int, int) or str
1975
1976            cell -- Cell
1977
1978        returns the cell with x and y updated
1979        """
1980        if cell is None:
1981            cell = Cell()
1982            clone = False
1983        if clone:
1984            cell = cell.clone
1985        x, y = self._translate_cell_coordinates(coord)
1986        if x is None:
1987            raise ValueError
1988        if y is None:
1989            raise ValueError
1990        row = self._get_row2(y, clone=True)
1991        row.y = y
1992        row.repeated = None
1993        cell_back = row.insert_cell(x, cell, clone=False)
1994        self.set_row(y, row, clone=False)
1995        # Update width if necessary
1996        self._update_width(row)
1997        return cell_back

Insert the given cell at the given coordinates. If no cell is given, an empty one is created.

Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Cells on the right are shifted. Other rows remain untouched.

Arguments:

coord -- (int, int) or str

cell -- Cell

returns the cell with x and y updated

def append_cell( self, y: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
1999    def append_cell(
2000        self,
2001        y: int | str,
2002        cell: Cell | None = None,
2003        clone: bool = True,
2004    ) -> Cell:
2005        """Append the given cell at the "y" coordinate. Repeated cells are
2006        accepted. If no cell is given, an empty one is created.
2007
2008        Position start at 0. So cell A4 is on row 3.
2009
2010        Other rows remain untouched.
2011
2012        Arguments:
2013
2014            y -- int or str
2015
2016            cell -- Cell
2017
2018        returns the cell with x and y updated
2019        """
2020        if cell is None:
2021            cell = Cell()
2022            clone = False
2023        if clone:
2024            cell = cell.clone
2025        y = self._translate_y_from_any(y)
2026        row = self._get_row2(y)
2027        row.y = y
2028        cell_back = row.append_cell(cell, clone=False)
2029        self.set_row(y, row)
2030        # Update width if necessary
2031        self._update_width(row)
2032        return cell_back

Append the given cell at the "y" coordinate. Repeated cells are accepted. If no cell is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

Other rows remain untouched.

Arguments:

y -- int or str

cell -- Cell

returns the cell with x and y updated

def delete_cell(self, coord: tuple | list | str) -> None:
2034    def delete_cell(self, coord: tuple | list | str) -> None:
2035        """Delete the cell at the given coordinates, so that next cells are
2036        shifted to the left.
2037
2038        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2039        human-readable position like "C4".
2040
2041        Use set_value() for erasing value.
2042
2043        Arguments:
2044
2045            coord -- (int, int) or str
2046        """
2047        x, y = self._translate_cell_coordinates(coord)
2048        if x is None:
2049            raise ValueError
2050        if y is None:
2051            raise ValueError
2052        # Outside the defined table
2053        if y >= self.height:
2054            return
2055        # Inside the defined table
2056        row = self._get_row2_base(y)
2057        if row is None:
2058            raise ValueError
2059        row.delete_cell(x)
2060        # self.set_row(y, row)

Delete the cell at the given coordinates, so that next cells are shifted to the left.

Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Use set_value() for erasing value.

Arguments:

coord -- (int, int) or str
def traverse_columns( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Column]:
2067    def traverse_columns(  # noqa: C901
2068        self,
2069        start: int | None = None,
2070        end: int | None = None,
2071    ) -> Iterator[Column]:
2072        """Yield as many column elements as expected columns in the table,
2073        i.e. expand repetitions by returning the same column as many times as
2074        necessary.
2075
2076            Arguments:
2077
2078                start -- int
2079
2080                end -- int
2081
2082        Copies are returned, use set_column() to push them back.
2083        """
2084        idx = -1
2085        before = -1
2086        x = 0
2087        if start is None and end is None:
2088            for juska in self._cmap:
2089                idx += 1
2090                if idx in self._indexes["_cmap"]:
2091                    column = self._indexes["_cmap"][idx]
2092                else:
2093                    column = self._get_element_idx2(_xpath_column_idx, idx)
2094                    self._indexes["_cmap"][idx] = column
2095                repeated = juska - before
2096                before = juska
2097                for _i in range(repeated or 1):
2098                    # Return a copy without the now obsolete repetition
2099                    column = column.clone
2100                    column.x = x
2101                    x += 1
2102                    if repeated > 1:
2103                        column.repeated = None
2104                    yield column
2105        else:
2106            if start is None:
2107                start = 0
2108            start = max(0, start)
2109            if end is None:
2110                try:
2111                    end = self._cmap[-1]
2112                except Exception:
2113                    end = -1
2114            start_map = find_odf_idx(self._cmap, start)
2115            if start_map is None:
2116                return
2117            if start_map > 0:
2118                before = self._cmap[start_map - 1]
2119            idx = start_map - 1
2120            before = start - 1
2121            x = start
2122            for juska in self._cmap[start_map:]:
2123                idx += 1
2124                if idx in self._indexes["_cmap"]:
2125                    column = self._indexes["_cmap"][idx]
2126                else:
2127                    column = self._get_element_idx2(_xpath_column_idx, idx)
2128                    self._indexes["_cmap"][idx] = column
2129                repeated = juska - before
2130                before = juska
2131                for _i in range(repeated or 1):
2132                    if x <= end:
2133                        column = column.clone
2134                        column.x = x
2135                        x += 1
2136                        if repeated > 1 or (x == start and start > 0):
2137                            column.repeated = None
2138                        yield column

Yield as many column elements as expected columns in the table, i.e. expand repetitions by returning the same column as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_column() to push them back.

def get_columns( self, coord: tuple | list | str | None = None, style: str | None = None) -> list[Column]:
2140    def get_columns(
2141        self,
2142        coord: tuple | list | str | None = None,
2143        style: str | None = None,
2144    ) -> list[Column]:
2145        """Get the list of columns matching the criteria. Each result is a
2146        tuple of (x, column).
2147
2148        Arguments:
2149
2150            coord -- str or tuple of int : coordinates of columns
2151
2152            style -- str
2153
2154        Return: list of columns
2155        """
2156        if coord:
2157            x, _y, _z, t = self._translate_column_coordinates(coord)
2158        else:
2159            x = t = None
2160        if not style:
2161            return list(self.traverse_columns(start=x, end=t))
2162        columns = []
2163        for column in self.traverse_columns(start=x, end=t):
2164            if style != column.style:
2165                continue
2166            columns.append(column)
2167        return columns

Get the list of columns matching the criteria. Each result is a tuple of (x, column).

Arguments:

coord -- str or tuple of int : coordinates of columns

style -- str

Return: list of columns

def get_column(self, x: int | str) -> Column:
2184    def get_column(self, x: int | str) -> Column:
2185        """Get the column at the given "x" position.
2186
2187        ODF columns don't contain cells, only style information.
2188
2189        Position start at 0. So cell C4 is on column 2. Alphabetical position
2190        like "C" is accepted.
2191
2192        A copy is returned, use set_column() to push it back.
2193
2194        Arguments:
2195
2196            x -- int or str
2197
2198        Return: Column
2199        """
2200        x = self._translate_x_from_any(x)
2201        column = self._get_column2(x)
2202        if column is None:
2203            raise ValueError
2204        column.x = x
2205        return column

Get the column at the given "x" position.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

A copy is returned, use set_column() to push it back.

Arguments:

x -- int or str

Return: Column

def set_column( self, x: int | str, column: Column | None = None) -> Column:
2207    def set_column(
2208        self,
2209        x: int | str,
2210        column: Column | None = None,
2211    ) -> Column:
2212        """Replace the column at the given "x" position.
2213
2214        ODF columns don't contain cells, only style information.
2215
2216        Position start at 0. So cell C4 is on column 2. Alphabetical position
2217        like "C" is accepted.
2218
2219        Arguments:
2220
2221            x -- int or str
2222
2223            column -- Column
2224        """
2225        x = self._translate_x_from_any(x)
2226        if column is None:
2227            column = Column()
2228            repeated = 1
2229        else:
2230            repeated = column.repeated or 1
2231        column.x = x
2232        # Outside the defined table ?
2233        diff = x - self.width
2234        if diff == 0:
2235            column_back = self.append_column(column, _repeated=repeated)
2236        elif diff > 0:
2237            self.append_column(Column(repeated=diff), _repeated=diff)
2238            column_back = self.append_column(column, _repeated=repeated)
2239        else:
2240            # Inside the defined table
2241            column_back = set_item_in_vault(  # type: ignore
2242                x, column, self, _xpath_column_idx, "_cmap"
2243            )
2244        return column_back

Replace the column at the given "x" position.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str

column -- Column
def insert_column( self, x: int | str, column: Column | None = None) -> Column:
2246    def insert_column(
2247        self,
2248        x: int | str,
2249        column: Column | None = None,
2250    ) -> Column:
2251        """Insert the column before the given "x" position. If no column is
2252        given, an empty one is created.
2253
2254        ODF columns don't contain cells, only style information.
2255
2256        Position start at 0. So cell C4 is on column 2. Alphabetical position
2257        like "C" is accepted.
2258
2259        Arguments:
2260
2261            x -- int or str
2262
2263            column -- Column
2264        """
2265        if column is None:
2266            column = Column()
2267        x = self._translate_x_from_any(x)
2268        diff = x - self.width
2269        if diff < 0:
2270            column_back = insert_item_in_vault(
2271                x, column, self, _xpath_column_idx, "_cmap"
2272            )
2273        elif diff == 0:
2274            column_back = self.append_column(column.clone)
2275        else:
2276            self.append_column(Column(repeated=diff), _repeated=diff)
2277            column_back = self.append_column(column.clone)
2278        column_back.x = x  # type: ignore
2279        # Repetitions are accepted
2280        repeated = column.repeated or 1
2281        # Update width on every row
2282        for row in self._get_rows():
2283            if row.width > x:
2284                row.insert_cell(x, Cell(repeated=repeated))
2285            # Shorter rows don't need insert
2286            # Longer rows shouldn't exist!
2287        return column_back  # type: ignore

Insert the column before the given "x" position. If no column is given, an empty one is created.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str

column -- Column
def append_column( self, column: Column | None = None, _repeated: int | None = None) -> Column:
2289    def append_column(
2290        self,
2291        column: Column | None = None,
2292        _repeated: int | None = None,
2293    ) -> Column:
2294        """Append the column at the end of the table. If no column is given,
2295        an empty one is created.
2296
2297        ODF columns don't contain cells, only style information.
2298
2299        Position start at 0. So cell C4 is on column 2. Alphabetical position
2300        like "C" is accepted.
2301
2302        Arguments:
2303
2304            column -- Column
2305        """
2306        if column is None:
2307            column = Column()
2308        else:
2309            column = column.clone
2310        if not self._cmap:
2311            position = 0
2312        else:
2313            odf_idx = len(self._cmap) - 1
2314            last_column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2315            if last_column is None:
2316                raise ValueError
2317            position = self.index(last_column) + 1
2318        column.x = self.width
2319        self.insert(column, position=position)
2320        # Repetitions are accepted
2321        if _repeated is None:
2322            _repeated = column.repeated or 1
2323        self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated)
2324        # No need to update row widths
2325        return column

Append the column at the end of the table. If no column is given, an empty one is created.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

column -- Column
def delete_column(self, x: int | str) -> None:
2327    def delete_column(self, x: int | str) -> None:
2328        """Delete the column at the given position. ODF columns don't contain
2329        cells, only style information.
2330
2331        Position start at 0. So cell C4 is on column 2. Alphabetical position
2332        like "C" is accepted.
2333
2334        Arguments:
2335
2336            x -- int or str
2337        """
2338        x = self._translate_x_from_any(x)
2339        # Outside the defined table
2340        if x >= self.width:
2341            return
2342        # Inside the defined table
2343        delete_item_in_vault(x, self, _xpath_column_idx, "_cmap")
2344        # Update width
2345        width = self.width
2346        for row in self._get_rows():
2347            if row.width >= width:
2348                row.delete_cell(x)

Delete the column at the given position. ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str
def get_column_cells( self, x: int | str, style: str | None = None, content: str | None = None, cell_type: str | None = None, complete: bool = False) -> list[Cell | None]:
2350    def get_column_cells(  # noqa: C901
2351        self,
2352        x: int | str,
2353        style: str | None = None,
2354        content: str | None = None,
2355        cell_type: str | None = None,
2356        complete: bool = False,
2357    ) -> list[Cell | None]:
2358        """Get the list of cells at the given position.
2359
2360        Position start at 0. So cell C4 is on column 2. Alphabetical position
2361        like "C" is accepted.
2362
2363        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2364        type, aka non empty cells.
2365
2366        If complete is True, replace missing values by None.
2367
2368        Arguments:
2369
2370            x -- int or str
2371
2372            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2373                         'currency', 'percentage' or 'all'
2374
2375            content -- str regex
2376
2377            style -- str
2378
2379            complete -- boolean
2380
2381        Return: list of Cell
2382        """
2383        x = self._translate_x_from_any(x)
2384        if cell_type:
2385            cell_type = cell_type.lower().strip()
2386        cells: list[Cell | None] = []
2387        if not style and not content and not cell_type:
2388            for row in self.traverse():
2389                cells.append(row.get_cell(x, clone=True))
2390            return cells
2391        for row in self.traverse():
2392            cell = row.get_cell(x, clone=True)
2393            if cell is None:
2394                raise ValueError
2395            # Filter the cells by cell_type
2396            if cell_type:
2397                ctype = cell.type
2398                if not ctype or not (ctype == cell_type or cell_type == "all"):
2399                    if complete:
2400                        cells.append(None)
2401                    continue
2402            # Filter the cells with the regex
2403            if content and not cell.match(content):
2404                if complete:
2405                    cells.append(None)
2406                continue
2407            # Filter the cells with the style
2408            if style and style != cell.style:
2409                if complete:
2410                    cells.append(None)
2411                continue
2412            cells.append(cell)
2413        return cells

Get the list of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.

If complete is True, replace missing values by None.

Arguments:

x -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

complete -- boolean

Return: list of Cell

def get_column_values( self, x: int | str, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> list[typing.Any]:
2415    def get_column_values(
2416        self,
2417        x: int | str,
2418        cell_type: str | None = None,
2419        complete: bool = True,
2420        get_type: bool = False,
2421    ) -> list[Any]:
2422        """Shortcut to get the list of Python values for the cells at the
2423        given position.
2424
2425        Position start at 0. So cell C4 is on column 2. Alphabetical position
2426        like "C" is accepted.
2427
2428        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2429        type, aka non empty cells.
2430        If cell_type and complete is True, replace missing values by None.
2431
2432        If get_type is True, returns a tuple (value, ODF type of value)
2433
2434        Arguments:
2435
2436            x -- int or str
2437
2438            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2439                         'currency', 'percentage' or 'all'
2440
2441            complete -- boolean
2442
2443            get_type -- boolean
2444
2445        Return: list of Python types
2446        """
2447        cells = self.get_column_cells(
2448            x, style=None, content=None, cell_type=cell_type, complete=complete
2449        )
2450        values: list[Any] = []
2451        for cell in cells:
2452            if cell is None:
2453                if complete:
2454                    if get_type:
2455                        values.append((None, None))
2456                    else:
2457                        values.append(None)
2458                continue
2459            if cell_type:
2460                ctype = cell.type
2461                if not ctype or not (ctype == cell_type or cell_type == "all"):
2462                    if complete:
2463                        if get_type:
2464                            values.append((None, None))
2465                        else:
2466                            values.append(None)
2467                    continue
2468            values.append(cell.get_value(get_type=get_type))
2469        return values

Shortcut to get the list of Python values for the cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.

If get_type is True, returns a tuple (value, ODF type of value)

Arguments:

x -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of Python types

def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2471    def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2472        """Shortcut to set the list of cells at the given position.
2473
2474        Position start at 0. So cell C4 is on column 2. Alphabetical position
2475        like "C" is accepted.
2476
2477        The list must have the same length than the table height.
2478
2479        Arguments:
2480
2481            x -- int or str
2482
2483            cells -- list of Cell
2484        """
2485        height = self.height
2486        if len(cells) != height:
2487            raise ValueError(f"col mismatch: {height} cells expected")
2488        cells_iterator = iter(cells)
2489        for y, row in enumerate(self.traverse()):
2490            row.set_cell(x, next(cells_iterator))
2491            self.set_row(y, row)

Shortcut to set the list of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

The list must have the same length than the table height.

Arguments:

x -- int or str

cells -- list of Cell
def set_column_values( self, x: int | str, values: list, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
2493    def set_column_values(
2494        self,
2495        x: int | str,
2496        values: list,
2497        cell_type: str | None = None,
2498        currency: str | None = None,
2499        style: str | None = None,
2500    ) -> None:
2501        """Shortcut to set the list of Python values of cells at the given
2502        position.
2503
2504        Position start at 0. So cell C4 is on column 2. Alphabetical position
2505        like "C" is accepted.
2506
2507        The list must have the same length than the table height.
2508
2509        Arguments:
2510
2511            x -- int or str
2512
2513            values -- list of Python types
2514
2515            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
2516                         'string' or 'time'
2517
2518            currency -- three-letter str
2519
2520            style -- str
2521        """
2522        cells = [
2523            Cell(value, cell_type=cell_type, currency=currency, style=style)
2524            for value in values
2525        ]
2526        self.set_column_cells(x, cells)

Shortcut to set the list of Python values of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

The list must have the same length than the table height.

Arguments:

x -- int or str

values -- list of Python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str
def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2528    def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2529        """Return wether every cell in the column at "x" position has no value
2530        or the value evaluates to False (empty string), and no style.
2531
2532        Position start at 0. So cell C4 is on column 2. Alphabetical position
2533        like "C" is accepted.
2534
2535        If aggressive is True, empty cells with style are considered empty.
2536
2537        Return: bool
2538        """
2539        for cell in self.get_column_cells(x):
2540            if cell is None:
2541                continue
2542            if not cell.is_empty(aggressive=aggressive):
2543                return False
2544        return True

Return wether every cell in the column at "x" position has no value or the value evaluates to False (empty string), and no style.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

If aggressive is True, empty cells with style are considered empty.

Return: bool

def get_named_ranges( self, table_name: str | list[str] | None = None) -> list[NamedRange]:
2548    def get_named_ranges(  # type: ignore
2549        self,
2550        table_name: str | list[str] | None = None,
2551    ) -> list[NamedRange]:
2552        """Returns the list of available Name Ranges of the spreadsheet. If
2553        table_name is provided, limits the search to these tables.
2554        Beware : named ranges are stored at the body level, thus do not call
2555        this method on a cloned table.
2556
2557        Arguments:
2558
2559            table_names -- str or list of str, names of tables
2560
2561        Return : list of table_range
2562        """
2563        body = self.document_body
2564        if not body:
2565            return []
2566        all_named_ranges = body.get_named_ranges()
2567        if not table_name:
2568            return all_named_ranges  # type:ignore
2569        filter_ = []
2570        if isinstance(table_name, str):
2571            filter_.append(table_name)
2572        elif isiterable(table_name):
2573            filter_.extend(table_name)
2574        else:
2575            raise ValueError(
2576                f"table_name must be string or Iterable, not {type(table_name)}"
2577            )
2578        return [
2579            nr for nr in all_named_ranges if nr.table_name in filter_  # type:ignore
2580        ]

Returns the list of available Name Ranges of the spreadsheet. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

table_names -- str or list of str, names of tables

Return : list of table_range

def get_named_range(self, name: str) -> NamedRange:
2582    def get_named_range(self, name: str) -> NamedRange:
2583        """Returns the Name Ranges of the specified name. If
2584        table_name is provided, limits the search to these tables.
2585        Beware : named ranges are stored at the body level, thus do not call
2586        this method on a cloned table.
2587
2588        Arguments:
2589
2590            name -- str, name of the named range object
2591
2592        Return : NamedRange
2593        """
2594        body = self.document_body
2595        if not body:
2596            raise ValueError("Table is not inside a document")
2597        return body.get_named_range(name)  # type: ignore

Returns the Name Ranges of the specified name. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str, name of the named range object

Return : NamedRange

def set_named_range( self, name: str, crange: str | tuple | list, table_name: str | None = None, usage: str | None = None) -> None:
2599    def set_named_range(
2600        self,
2601        name: str,
2602        crange: str | tuple | list,
2603        table_name: str | None = None,
2604        usage: str | None = None,
2605    ) -> None:
2606        """Create a Named Range element and insert it in the document.
2607        Beware : named ranges are stored at the body level, thus do not call
2608        this method on a cloned table.
2609
2610        Arguments:
2611
2612            name -- str, name of the named range
2613
2614            crange -- str or tuple of int, cell or area coordinate
2615
2616            table_name -- str, name of the table
2617
2618            uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2619        """
2620        body = self.document_body
2621        if not body:
2622            raise ValueError("Table is not inside a document")
2623        if not name:
2624            raise ValueError("Name required.")
2625        if table_name is None:
2626            table_name = self.name
2627        named_range = NamedRange(name, crange, table_name, usage)
2628        body.append_named_range(named_range)

Create a Named Range element and insert it in the document. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str, name of the named range

crange -- str or tuple of int, cell or area coordinate

table_name -- str, name of the table

uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
def delete_named_range(self, name: str) -> None:
2630    def delete_named_range(self, name: str) -> None:
2631        """Delete the Named Range of specified name from the spreadsheet.
2632        Beware : named ranges are stored at the body level, thus do not call
2633        this method on a cloned table.
2634
2635        Arguments:
2636
2637            name -- str
2638        """
2639        name = name.strip()
2640        if not name:
2641            raise ValueError("Name required.")
2642        body = self.document_body
2643        if not body:
2644            raise ValueError("Table is not inside a document.")
2645        body.delete_named_range(name)

Delete the Named Range of specified name from the spreadsheet. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str
def set_span(self, area: str | tuple | list, merge: bool = False) -> bool:
2651    def set_span(  # noqa: C901
2652        self,
2653        area: str | tuple | list,
2654        merge: bool = False,
2655    ) -> bool:
2656        """Create a Cell Span : span the first cell of the area on several
2657        columns and/or rows.
2658        If merge is True, replace text of the cell by the concatenation of
2659        existing text in covered cells.
2660        Beware : if merge is True, old text is changed, if merge is False
2661        (the default), old text in coverd cells is still present but not
2662        displayed by most GUI.
2663
2664        If the area defines only one cell, the set span will do nothing.
2665        It is not allowed to apply set span to an area whose one cell already
2666        belongs to previous cell span.
2667
2668        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2669        be provided as an alpha numeric value like "A1:B2' or a tuple like
2670        (0, 0, 1, 1) or (0, 0).
2671
2672        Arguments:
2673
2674            area -- str or tuple of int, cell or area coordinate
2675
2676            merge -- boolean
2677        """
2678        # get area
2679        digits = convert_coordinates(area)
2680        if len(digits) == 4:
2681            x, y, z, t = digits
2682        else:
2683            x, y = digits
2684            z, t = digits
2685        start = x, y
2686        end = z, t
2687        if start == end:
2688            # one cell : do nothing
2689            return False
2690        if x is None:
2691            raise ValueError
2692        if y is None:
2693            raise ValueError
2694        if z is None:
2695            raise ValueError
2696        if t is None:
2697            raise ValueError
2698        # check for previous span
2699        good = True
2700        # Check boundaries and empty cells : need to crate non existent cells
2701        # so don't use get_cells directly, but get_cell
2702        cells = []
2703        for yy in range(y, t + 1):
2704            row_cells = []
2705            for xx in range(x, z + 1):
2706                row_cells.append(
2707                    self.get_cell((xx, yy), clone=True, keep_repeated=False)
2708                )
2709            cells.append(row_cells)
2710        for row in cells:
2711            for cell in row:
2712                if cell._is_spanned():
2713                    good = False
2714                    break
2715            if not good:
2716                break
2717        if not good:
2718            return False
2719        # Check boundaries
2720        # if z >= self.width or t >= self.height:
2721        #    self.set_cell(coord = end)
2722        #    print area, z, t
2723        #    cells = self.get_cells((x, y, z, t))
2724        #    print cells
2725        # do it:
2726        if merge:
2727            val_list = []
2728            for row in cells:
2729                for cell in row:
2730                    if cell.is_empty(aggressive=True):
2731                        continue
2732                    val = cell.get_value()
2733                    if val is not None:
2734                        if isinstance(val, str):
2735                            val.strip()
2736                        if val != "":
2737                            val_list.append(val)
2738                        cell.clear()
2739            if val_list:
2740                if len(val_list) == 1:
2741                    cells[0][0].set_value(val_list[0])
2742                else:
2743                    value = " ".join([str(v) for v in val_list if v])
2744                    cells[0][0].set_value(value)
2745        cols = z - x + 1
2746        cells[0][0].set_attribute("table:number-columns-spanned", str(cols))
2747        rows = t - y + 1
2748        cells[0][0].set_attribute("table:number-rows-spanned", str(rows))
2749        for cell in cells[0][1:]:
2750            cell.tag = "table:covered-table-cell"
2751        for row in cells[1:]:
2752            for cell in row:
2753                cell.tag = "table:covered-table-cell"
2754        # replace cells in table
2755        self.set_cells(cells, coord=start, clone=False)
2756        return True

Create a Cell Span : span the first cell of the area on several columns and/or rows. If merge is True, replace text of the cell by the concatenation of existing text in covered cells. Beware : if merge is True, old text is changed, if merge is False (the default), old text in coverd cells is still present but not displayed by most GUI.

If the area defines only one cell, the set span will do nothing. It is not allowed to apply set span to an area whose one cell already belongs to previous cell span.

Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).

Arguments:

area -- str or tuple of int, cell or area coordinate

merge -- boolean
def del_span(self, area: str | tuple | list) -> bool:
2758    def del_span(self, area: str | tuple | list) -> bool:
2759        """Delete a Cell Span. 'area' is the cell coordiante of the upper left
2760        cell of the spanned area.
2761
2762        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2763        be provided as an alpha numeric value like "A1:B2' or a tuple like
2764        (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell
2765        is used.
2766
2767        Arguments:
2768
2769            area -- str or tuple of int, cell or area coordinate
2770        """
2771        # get area
2772        digits = convert_coordinates(area)
2773        if len(digits) == 4:
2774            x, y, _z, _t = digits
2775        else:
2776            x, y = digits
2777        if x is None:
2778            raise ValueError
2779        if y is None:
2780            raise ValueError
2781        start = x, y
2782        # check for previous span
2783        cell0 = self.get_cell(start)
2784        nb_cols = cell0.get_attribute_integer("table:number-columns-spanned")
2785        if nb_cols is None:
2786            return False
2787        nb_rows = cell0.get_attribute_integer("table:number-rows-spanned")
2788        if nb_rows is None:
2789            return False
2790        z = x + nb_cols - 1
2791        t = y + nb_rows - 1
2792        cells = self.get_cells((x, y, z, t))
2793        cells[0][0].del_attribute("table:number-columns-spanned")
2794        cells[0][0].del_attribute("table:number-rows-spanned")
2795        for cell in cells[0][1:]:
2796            cell.tag = "table:table-cell"
2797        for row in cells[1:]:
2798            for cell in row:
2799                cell.tag = "table:table-cell"
2800        # replace cells in table
2801        self.set_cells(cells, coord=start, clone=False)
2802        return True

Delete a Cell Span. 'area' is the cell coordiante of the upper left cell of the spanned area.

Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell is used.

Arguments:

area -- str or tuple of int, cell or area coordinate
def to_csv( self, path_or_file: str | pathlib.Path | None = None, dialect: str = 'excel') -> Any:
2806    def to_csv(
2807        self,
2808        path_or_file: str | Path | None = None,
2809        dialect: str = "excel",
2810    ) -> Any:
2811        """Write the table as CSV in the file.
2812
2813        If the file is a string, it is opened as a local path. Else an
2814        opened file-like is expected.
2815
2816        Arguments:
2817
2818            path_or_file -- str or file-like
2819
2820            dialect -- str, python csv.dialect, can be 'excel', 'unix'...
2821        """
2822
2823        def write_content(csv_writer: object) -> None:
2824            for values in self.iter_values():
2825                line = []
2826                for value in values:
2827                    if value is None:
2828                        value = ""
2829                    if isinstance(value, str):
2830                        value = value.strip()
2831                    line.append(value)
2832                csv_writer.writerow(line)  # type: ignore
2833
2834        out = StringIO(newline="")
2835        csv_writer = csv.writer(out, dialect=dialect)
2836        write_content(csv_writer)
2837        if path_or_file is None:
2838            return out.getvalue()
2839        path = Path(path_or_file)
2840        path.write_text(out.getvalue())
2841        return None

Write the table as CSV in the file.

If the file is a string, it is opened as a local path. Else an opened file-like is expected.

Arguments:

path_or_file -- str or file-like

dialect -- str, python csv.dialect, can be 'excel', 'unix'...
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
append_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Text(builtins.str):
273class Text(str):
274    """Representation of an XML text node. Created to hide the specifics of
275    lxml in searching text nodes using XPath.
276
277    Constructed like any str object but only accepts lxml text objects.
278    """
279
280    # There's some black magic in inheriting from str
281    def __init__(
282        self,
283        text_result: _ElementUnicodeResult | _ElementStringResult,
284    ) -> None:
285        self.__parent = text_result.getparent()
286        self.__is_text = text_result.is_text
287        self.__is_tail = text_result.is_tail
288
289    @property
290    def parent(self) -> Element | None:
291        parent = self.__parent
292        # XXX happens just because of the unit test
293        if parent is None:
294            return None
295        return Element.from_tag(tag_or_elem=parent)
296
297    def is_text(self) -> bool:
298        return self.__is_text
299
300    def is_tail(self) -> bool:
301        return self.__is_tail

Representation of an XML text node. Created to hide the specifics of lxml in searching text nodes using XPath.

Constructed like any str object but only accepts lxml text objects.

Text( text_result: lxml.etree._ElementUnicodeResult | lxml.etree._ElementStringResult)
281    def __init__(
282        self,
283        text_result: _ElementUnicodeResult | _ElementStringResult,
284    ) -> None:
285        self.__parent = text_result.getparent()
286        self.__is_text = text_result.is_text
287        self.__is_tail = text_result.is_tail
parent: Element | None
289    @property
290    def parent(self) -> Element | None:
291        parent = self.__parent
292        # XXX happens just because of the unit test
293        if parent is None:
294            return None
295        return Element.from_tag(tag_or_elem=parent)
def is_text(self) -> bool:
297    def is_text(self) -> bool:
298        return self.__is_text
def is_tail(self) -> bool:
300    def is_tail(self) -> bool:
301        return self.__is_tail
Inherited Members
builtins.str
encode
replace
split
rsplit
join
capitalize
casefold
title
center
count
expandtabs
find
partition
index
ljust
lower
lstrip
rfind
rindex
rjust
rstrip
rpartition
splitlines
strip
swapcase
translate
upper
startswith
endswith
removeprefix
removesuffix
isascii
islower
isupper
istitle
isspace
isdecimal
isdigit
isnumeric
isalpha
isalnum
isidentifier
isprintable
zfill
format
format_map
maketrans
class TextChange(odfdo.Element):
508class TextChange(Element):
509    """The TextChange "text:change" element marks a position in an empty
510    region where text has been deleted.
511    """
512
513    _tag = "text:change"
514
515    def get_id(self) -> str | None:
516        return self.get_attribute_string("text:change-id")
517
518    def set_id(self, idx: str) -> None:
519        self.set_attribute("text:change-id", idx)
520
521    def _get_tracked_changes(self) -> Element | None:
522        body = self.document_body
523        if not body:
524            raise ValueError
525        return body.get_tracked_changes()
526
527    def get_changed_region(
528        self,
529        tracked_changes: Element | None = None,
530    ) -> Element | None:
531        if not tracked_changes:
532            tracked_changes = self._get_tracked_changes()
533        idx = self.get_id()
534        return tracked_changes.get_changed_region(text_id=idx)  # type: ignore
535
536    def get_change_info(
537        self,
538        tracked_changes: Element | None = None,
539    ) -> Element | None:
540        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
541        if not changed_region:
542            return None
543        return changed_region.get_change_info()  # type: ignore
544
545    def get_change_element(
546        self,
547        tracked_changes: Element | None = None,
548    ) -> Element | None:
549        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
550        if not changed_region:
551            return None
552        return changed_region.get_change_element()  # type: ignore
553
554    def get_deleted(
555        self,
556        tracked_changes: Element | None = None,
557        as_text: bool = False,
558        no_header: bool = False,
559        clean: bool = True,
560    ) -> Element | None:
561        """Shortcut to get the deleted informations stored in the
562        TextDeletion stored in the tracked changes.
563
564        Return: Paragraph (or None)."
565        """
566        changed = self.get_change_element(tracked_changes=tracked_changes)
567        if not changed:
568            return None
569        return changed.get_deleted(  # type: ignore
570            as_text=as_text,
571            no_header=no_header,
572            clean=clean,
573        )
574
575    def get_inserted(
576        self,
577        as_text: bool = False,
578        no_header: bool = False,
579        clean: bool = True,
580    ) -> str | Element | list[Element] | None:
581        """Return None."""
582        return None
583
584    def get_start(self) -> TextChangeStart | None:
585        """Return None."""
586        return None
587
588    def get_end(self) -> TextChangeEnd | None:
589        """Return None."""
590        return None

The TextChange "text:change" element marks a position in an empty region where text has been deleted.

def get_id(self) -> str | None:
515    def get_id(self) -> str | None:
516        return self.get_attribute_string("text:change-id")
def set_id(self, idx: str) -> None:
518    def set_id(self, idx: str) -> None:
519        self.set_attribute("text:change-id", idx)
def get_changed_region( self, tracked_changes: Element | None = None) -> Element | None:
527    def get_changed_region(
528        self,
529        tracked_changes: Element | None = None,
530    ) -> Element | None:
531        if not tracked_changes:
532            tracked_changes = self._get_tracked_changes()
533        idx = self.get_id()
534        return tracked_changes.get_changed_region(text_id=idx)  # type: ignore
def get_change_info( self, tracked_changes: Element | None = None) -> Element | None:
536    def get_change_info(
537        self,
538        tracked_changes: Element | None = None,
539    ) -> Element | None:
540        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
541        if not changed_region:
542            return None
543        return changed_region.get_change_info()  # type: ignore
def get_change_element( self, tracked_changes: Element | None = None) -> Element | None:
545    def get_change_element(
546        self,
547        tracked_changes: Element | None = None,
548    ) -> Element | None:
549        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
550        if not changed_region:
551            return None
552        return changed_region.get_change_element()  # type: ignore
def get_deleted( self, tracked_changes: Element | None = None, as_text: bool = False, no_header: bool = False, clean: bool = True) -> Element | None:
554    def get_deleted(
555        self,
556        tracked_changes: Element | None = None,
557        as_text: bool = False,
558        no_header: bool = False,
559        clean: bool = True,
560    ) -> Element | None:
561        """Shortcut to get the deleted informations stored in the
562        TextDeletion stored in the tracked changes.
563
564        Return: Paragraph (or None)."
565        """
566        changed = self.get_change_element(tracked_changes=tracked_changes)
567        if not changed:
568            return None
569        return changed.get_deleted(  # type: ignore
570            as_text=as_text,
571            no_header=no_header,
572            clean=clean,
573        )

Shortcut to get the deleted informations stored in the TextDeletion stored in the tracked changes.

Return: Paragraph (or None)."

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
575    def get_inserted(
576        self,
577        as_text: bool = False,
578        no_header: bool = False,
579        clean: bool = True,
580    ) -> str | Element | list[Element] | None:
581        """Return None."""
582        return None

Return None.

def get_start(self) -> TextChangeStart | None:
584    def get_start(self) -> TextChangeStart | None:
585        """Return None."""
586        return None

Return None.

def get_end(self) -> TextChangeEnd | None:
588    def get_end(self) -> TextChangeEnd | None:
589        """Return None."""
590        return None

Return None.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TextChangeEnd(odfdo.TextChange):
593class TextChangeEnd(TextChange):
594    """The TextChangeEnd "text:change-end" element marks the end of a region
595    with content where text has been inserted or the format has been
596    changed.
597    """
598
599    _tag = "text:change-end"
600
601    def get_start(self) -> TextChangeStart | None:
602        """Return the corresponding annotation starting tag or None."""
603        idx = self.get_id()
604        parent = self.parent
605        if parent is None:
606            raise ValueError("Can not find end tag: no parent available.")
607        body = self.document_body
608        if not body:
609            body = self.root
610        return body.get_text_change_start(idx=idx)  # type: ignore
611
612    def get_end(self) -> TextChangeEnd | None:
613        """Return self."""
614        return self
615
616    def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
617        """Return None."""
618        return None
619
620    def get_inserted(
621        self,
622        as_text: bool = False,
623        no_header: bool = False,
624        clean: bool = True,
625    ) -> str | Element | list[Element] | None:
626        """Return the content between text:change-start and text:change-end.
627
628        If no content exists (deletion tag), returns None (or '' if text flag
629        is True).
630        If as_text is True: returns the text content.
631        If clean is True: suppress unwanted tags (deletions marks, ...)
632        If no_header is True: existing text:h are changed in text:p
633        By default: returns a list of Element, cleaned and with headers
634
635        Arguments:
636
637            as_text -- boolean
638
639            clean -- boolean
640
641            no_header -- boolean
642
643        Return: list or Element or text
644        """
645
646        # idx = self.get_id()
647        start = self.get_start()
648        end = self.get_end()
649        if end is None or start is None:
650            if as_text:
651                return ""
652            return None
653        body = self.document_body
654        if not body:
655            body = self.root
656        return body.get_between(
657            start, end, as_text=as_text, no_header=no_header, clean=clean
658        )

The TextChangeEnd "text:change-end" element marks the end of a region with content where text has been inserted or the format has been changed.

def get_start(self) -> TextChangeStart | None:
601    def get_start(self) -> TextChangeStart | None:
602        """Return the corresponding annotation starting tag or None."""
603        idx = self.get_id()
604        parent = self.parent
605        if parent is None:
606            raise ValueError("Can not find end tag: no parent available.")
607        body = self.document_body
608        if not body:
609            body = self.root
610        return body.get_text_change_start(idx=idx)  # type: ignore

Return the corresponding annotation starting tag or None.

def get_end(self) -> TextChangeEnd | None:
612    def get_end(self) -> TextChangeEnd | None:
613        """Return self."""
614        return self

Return self.

def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
616    def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
617        """Return None."""
618        return None

Return None.

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
620    def get_inserted(
621        self,
622        as_text: bool = False,
623        no_header: bool = False,
624        clean: bool = True,
625    ) -> str | Element | list[Element] | None:
626        """Return the content between text:change-start and text:change-end.
627
628        If no content exists (deletion tag), returns None (or '' if text flag
629        is True).
630        If as_text is True: returns the text content.
631        If clean is True: suppress unwanted tags (deletions marks, ...)
632        If no_header is True: existing text:h are changed in text:p
633        By default: returns a list of Element, cleaned and with headers
634
635        Arguments:
636
637            as_text -- boolean
638
639            clean -- boolean
640
641            no_header -- boolean
642
643        Return: list or Element or text
644        """
645
646        # idx = self.get_id()
647        start = self.get_start()
648        end = self.get_end()
649        if end is None or start is None:
650            if as_text:
651                return ""
652            return None
653        body = self.document_body
654        if not body:
655            body = self.root
656        return body.get_between(
657            start, end, as_text=as_text, no_header=no_header, clean=clean
658        )

Return the content between text:change-start and text:change-end.

If no content exists (deletion tag), returns None (or '' if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextChange
get_id
set_id
get_changed_region
get_change_info
get_change_element
class TextChangeStart(odfdo.TextChangeEnd):
661class TextChangeStart(TextChangeEnd):
662    """The TextChangeStart "text:change-start" element marks the start of a
663    region with content where text has been inserted or the format has
664    been changed.
665    """
666
667    _tag = "text:change-start"
668
669    def get_start(self) -> TextChangeStart:
670        """Return self."""
671        return self
672
673    def get_end(self) -> TextChangeEnd:
674        """Return the corresponding change-end tag or None."""
675        idx = self.get_id()
676        parent = self.parent
677        if parent is None:
678            raise ValueError("Can not find end tag: no parent available.")
679        body = self.document_body
680        if not body:
681            body = self.root
682        return body.get_text_change_end(idx=idx)  # type: ignore
683
684    def delete(
685        self,
686        child: Element | None = None,
687        keep_tail: bool = True,
688    ) -> None:
689        """Delete the given element from the XML tree. If no element is given,
690        "self" is deleted. The XML library may allow to continue to use an
691        element now "orphan" as long as you have a reference to it.
692
693        For TextChangeStart : delete also the end tag if exists.
694
695        Arguments:
696
697            child -- Element
698
699            keep_tail -- boolean (default to True), True for most usages.
700        """
701        if child is not None:  # act like normal delete
702            return super().delete(child, keep_tail)
703        idx = self.get_id()
704        parent = self.parent
705        if parent is None:
706            raise ValueError("cannot delete the root element")
707        body = self.document_body
708        if not body:
709            body = parent
710        end = body.get_text_change_end(idx=idx)
711        if end:
712            end.delete()
713        # act like normal delete
714        super().delete()

The TextChangeStart "text:change-start" element marks the start of a region with content where text has been inserted or the format has been changed.

def get_start(self) -> TextChangeStart:
669    def get_start(self) -> TextChangeStart:
670        """Return self."""
671        return self

Return self.

def get_end(self) -> TextChangeEnd:
673    def get_end(self) -> TextChangeEnd:
674        """Return the corresponding change-end tag or None."""
675        idx = self.get_id()
676        parent = self.parent
677        if parent is None:
678            raise ValueError("Can not find end tag: no parent available.")
679        body = self.document_body
680        if not body:
681            body = self.root
682        return body.get_text_change_end(idx=idx)  # type: ignore

Return the corresponding change-end tag or None.

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
684    def delete(
685        self,
686        child: Element | None = None,
687        keep_tail: bool = True,
688    ) -> None:
689        """Delete the given element from the XML tree. If no element is given,
690        "self" is deleted. The XML library may allow to continue to use an
691        element now "orphan" as long as you have a reference to it.
692
693        For TextChangeStart : delete also the end tag if exists.
694
695        Arguments:
696
697            child -- Element
698
699            keep_tail -- boolean (default to True), True for most usages.
700        """
701        if child is not None:  # act like normal delete
702            return super().delete(child, keep_tail)
703        idx = self.get_id()
704        parent = self.parent
705        if parent is None:
706            raise ValueError("cannot delete the root element")
707        body = self.document_body
708        if not body:
709            body = parent
710        end = body.get_text_change_end(idx=idx)
711        if end:
712            end.delete()
713        # act like normal delete
714        super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For TextChangeStart : delete also the end tag if exists.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextChangeEnd
get_deleted
get_inserted
TextChange
get_id
set_id
get_changed_region
get_change_info
get_change_element
class TextChangedRegion(odfdo.Element):
358class TextChangedRegion(Element):
359    """Each TextChangedRegion "text:changed-region" element contains a single
360    element, one of TextInsertion, TextDeletion or TextFormatChange that
361    corresponds to a change being tracked within the scope of the
362    "text:tracked-changes" element that contains the "text:changed-region"
363    instance.
364    The xml:id attribute of the TextChangedRegion is referenced
365    from the "text:change", "text:change-start" and "text:change-end"
366    elements that identify where the change applies to markup in the scope of
367    the "text:tracked-changes" element.
368
369    Warning : for this implementation, text:change should be referenced only
370              once in the scope, which is different from ODF 1.2 requirement:
371             " A "text:changed-region" can be referenced by more than one
372             change, but the corresponding referencing change mark elements
373             shall be of the same change type - insertion, format change or
374             deletion. "
375    """
376
377    _tag = "text:changed-region"
378
379    def get_change_info(self) -> Element | None:
380        """Shortcut to get the ChangeInfo element of the change
381        element child.
382
383        Return: ChangeInfo element.
384        """
385        return self.get_element("descendant::office:change-info")
386
387    def set_change_info(
388        self,
389        change_info: Element | None = None,
390        creator: str | None = None,
391        date: datetime | None = None,
392        comments: Element | list[Element] | None = None,
393    ) -> None:
394        """Shortcut to set the ChangeInfo element of the sub change element.
395        See TextInsertion.set_change_info() for details.
396
397        Arguments:
398
399             change_info -- ChangeInfo element (or None)
400
401             cretor -- str (or None)
402
403             date -- datetime (or None)
404
405             comments -- Paragraph or list of Paragraph elements (or None)
406        """
407        child = self.get_change_element()
408        if not child:
409            raise ValueError
410        child.set_change_info(  # type: ignore
411            change_info=change_info, creator=creator, date=date, comments=comments
412        )
413
414    def get_change_element(self) -> Element | None:
415        """Get the change element child. It can be either: TextInsertion,
416        TextDeletion, or TextFormatChange as an Element object.
417
418        Return: Element.
419        """
420        request = (
421            "descendant::text:insertion "
422            "| descendant::text:deletion"
423            "| descendant::text:format-change"
424        )
425        return self._filtered_element(request, 0)
426
427    def _get_text_id(self) -> str | None:
428        return self.get_attribute_string("text:id")
429
430    def _set_text_id(self, text_id: str) -> None:
431        self.set_attribute("text:id", text_id)
432
433    def _get_xml_id(self) -> str | None:
434        return self.get_attribute_string("xml:id")
435
436    def _set_xml_id(self, xml_id: str) -> None:
437        self.set_attribute("xml:id", xml_id)
438
439    def get_id(self) -> str | None:
440        """Get the "text:id" attribute.
441
442        Return: str
443        """
444        return self._get_text_id()
445
446    def set_id(self, idx: str) -> None:
447        """Set both the "text:id" and "xml:id" attributes with same value."""
448        self._set_text_id(idx)
449        self._set_xml_id(idx)

Each TextChangedRegion "text:changed-region" element contains a single element, one of TextInsertion, TextDeletion or TextFormatChange that corresponds to a change being tracked within the scope of the "text:tracked-changes" element that contains the "text:changed-region" instance. The xml:id attribute of the TextChangedRegion is referenced from the "text:change", "text:change-start" and "text:change-end" elements that identify where the change applies to markup in the scope of the "text:tracked-changes" element.

Warning : for this implementation, text:change should be referenced only once in the scope, which is different from ODF 1.2 requirement: " A "text:changed-region" can be referenced by more than one change, but the corresponding referencing change mark elements shall be of the same change type - insertion, format change or deletion. "

def get_change_info(self) -> Element | None:
379    def get_change_info(self) -> Element | None:
380        """Shortcut to get the ChangeInfo element of the change
381        element child.
382
383        Return: ChangeInfo element.
384        """
385        return self.get_element("descendant::office:change-info")

Shortcut to get the ChangeInfo element of the change element child.

Return: ChangeInfo element.

def set_change_info( self, change_info: Element | None = None, creator: str | None = None, date: datetime.datetime | None = None, comments: Element | list[Element] | None = None) -> None:
387    def set_change_info(
388        self,
389        change_info: Element | None = None,
390        creator: str | None = None,
391        date: datetime | None = None,
392        comments: Element | list[Element] | None = None,
393    ) -> None:
394        """Shortcut to set the ChangeInfo element of the sub change element.
395        See TextInsertion.set_change_info() for details.
396
397        Arguments:
398
399             change_info -- ChangeInfo element (or None)
400
401             cretor -- str (or None)
402
403             date -- datetime (or None)
404
405             comments -- Paragraph or list of Paragraph elements (or None)
406        """
407        child = self.get_change_element()
408        if not child:
409            raise ValueError
410        child.set_change_info(  # type: ignore
411            change_info=change_info, creator=creator, date=date, comments=comments
412        )

Shortcut to set the ChangeInfo element of the sub change element. See TextInsertion.set_change_info() for details.

Arguments:

 change_info -- ChangeInfo element (or None)

 cretor -- str (or None)

 date -- datetime (or None)

 comments -- Paragraph or list of Paragraph elements (or None)
def get_change_element(self) -> Element | None:
414    def get_change_element(self) -> Element | None:
415        """Get the change element child. It can be either: TextInsertion,
416        TextDeletion, or TextFormatChange as an Element object.
417
418        Return: Element.
419        """
420        request = (
421            "descendant::text:insertion "
422            "| descendant::text:deletion"
423            "| descendant::text:format-change"
424        )
425        return self._filtered_element(request, 0)

Get the change element child. It can be either: TextInsertion, TextDeletion, or TextFormatChange as an Element object.

Return: Element.

def get_id(self) -> str | None:
439    def get_id(self) -> str | None:
440        """Get the "text:id" attribute.
441
442        Return: str
443        """
444        return self._get_text_id()

Get the "text:id" attribute.

Return: str

def set_id(self, idx: str) -> None:
446    def set_id(self, idx: str) -> None:
447        """Set both the "text:id" and "xml:id" attributes with same value."""
448        self._set_text_id(idx)
449        self._set_xml_id(idx)

Set both the "text:id" and "xml:id" attributes with same value.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TextDeletion(odfdo.TextInsertion):
251class TextDeletion(TextInsertion):
252    """The TextDeletion "text:deletion" contains information that identifies
253    the person responsible for a deletion and the date of that deletion.
254    This information may also contain one or more Paragraph which contains
255    a comment on the deletion. The TextDeletion element may also contain
256    content that was deleted while change tracking was enabled. The position
257    where the text was deleted is marked by a "text:change" element. Deleted
258    text is contained in a paragraph element. To reconstruct the original
259    text, the paragraph containing the deleted text is merged with its
260    surrounding paragraph or heading element. To reconstruct the text before
261    a deletion took place:
262      - If the change mark is inside a paragraph, insert the content that was
263      deleted, but remove all leading start tags up to and including the
264      first "text:p" element and all trailing end tags up to and including
265      the last "/text:p" or "/text:h" element. If the last trailing element
266      is a "/text:h", change the end tag "/text:p" following this insertion
267      to a "/text:h" element.
268      - If the change mark is inside a heading, insert the content that was
269      deleted, but remove all leading start tags up to and including the
270      first "text:h" element and all trailing end tags up to and including
271      the last "/text:h" or "/text:p" element. If the last trailing element
272      is a "/text:p", change the end tag "/text:h" following this insertion
273      to a "/text:p" element.
274      - Otherwise, copy the text content of the "text:deletion" element in
275      place of the change mark.
276    """
277
278    _tag = "text:deletion"
279
280    def get_deleted(
281        self,
282        as_text: bool = False,
283        no_header: bool = False,
284    ) -> str | list[Element] | None:
285        """Get the deleted informations stored in the TextDeletion.
286        If as_text is True: returns the text content.
287        If no_header is True: existing Heading are changed in Paragraph
288
289        Arguments:
290
291            as_text -- boolean
292
293            no_header -- boolean
294
295        Return: Paragraph and Header list
296        """
297        children = self.children
298        inner = [elem for elem in children if elem.tag != "office:change-info"]
299        if no_header:  # crude replace t:h by t:p
300            new_inner = []
301            for element in inner:
302                if element.tag == "text:h":
303                    children = element.children
304                    text = element.text
305                    para = Element.from_tag("text:p")
306                    para.text = text
307                    for child in children:
308                        para.append(child)
309                    new_inner.append(para)
310                else:
311                    new_inner.append(element)
312            inner = new_inner
313        if as_text:
314            return "\n".join([elem.get_formatted_text(context=None) for elem in inner])
315        return inner
316
317    def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None:
318        """Set the deleted informations stored in the TextDeletion. An
319        actual content that was deleted is expected, embeded in a Paragraph
320        element or Header.
321
322        Arguments:
323
324            paragraph_or_list -- Paragraph or Header element (or list)
325        """
326        for element in self.get_deleted():  # type: ignore
327            self.delete(element)  # type: ignore
328        if isinstance(paragraph_or_list, Element):
329            paragraph_or_list = [paragraph_or_list]
330        for element in paragraph_or_list:
331            self.append(element)
332
333    def get_inserted(
334        self,
335        as_text: bool = False,
336        no_header: bool = False,
337        clean: bool = True,
338    ) -> str | Element | list[Element] | None:
339        """Return None."""
340        if as_text:
341            return ""
342        return None

The TextDeletion "text:deletion" contains information that identifies the person responsible for a deletion and the date of that deletion. This information may also contain one or more Paragraph which contains a comment on the deletion. The TextDeletion element may also contain content that was deleted while change tracking was enabled. The position where the text was deleted is marked by a "text:change" element. Deleted text is contained in a paragraph element. To reconstruct the original text, the paragraph containing the deleted text is merged with its surrounding paragraph or heading element. To reconstruct the text before a deletion took place:

  • If the change mark is inside a paragraph, insert the content that was deleted, but remove all leading start tags up to and including the first "text:p" element and all trailing end tags up to and including the last "/text:p" or "/text:h" element. If the last trailing element is a "/text:h", change the end tag "/text:p" following this insertion to a "/text:h" element.
  • If the change mark is inside a heading, insert the content that was deleted, but remove all leading start tags up to and including the first "text:h" element and all trailing end tags up to and including the last "/text:h" or "/text:p" element. If the last trailing element is a "/text:p", change the end tag "/text:h" following this insertion to a "/text:p" element.
  • Otherwise, copy the text content of the "text:deletion" element in place of the change mark.
def get_deleted( self, as_text: bool = False, no_header: bool = False) -> str | list[Element] | None:
280    def get_deleted(
281        self,
282        as_text: bool = False,
283        no_header: bool = False,
284    ) -> str | list[Element] | None:
285        """Get the deleted informations stored in the TextDeletion.
286        If as_text is True: returns the text content.
287        If no_header is True: existing Heading are changed in Paragraph
288
289        Arguments:
290
291            as_text -- boolean
292
293            no_header -- boolean
294
295        Return: Paragraph and Header list
296        """
297        children = self.children
298        inner = [elem for elem in children if elem.tag != "office:change-info"]
299        if no_header:  # crude replace t:h by t:p
300            new_inner = []
301            for element in inner:
302                if element.tag == "text:h":
303                    children = element.children
304                    text = element.text
305                    para = Element.from_tag("text:p")
306                    para.text = text
307                    for child in children:
308                        para.append(child)
309                    new_inner.append(para)
310                else:
311                    new_inner.append(element)
312            inner = new_inner
313        if as_text:
314            return "\n".join([elem.get_formatted_text(context=None) for elem in inner])
315        return inner

Get the deleted informations stored in the TextDeletion. If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph

Arguments:

as_text -- boolean

no_header -- boolean

Return: Paragraph and Header list

def set_deleted( self, paragraph_or_list: Element | list[Element]) -> None:
317    def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None:
318        """Set the deleted informations stored in the TextDeletion. An
319        actual content that was deleted is expected, embeded in a Paragraph
320        element or Header.
321
322        Arguments:
323
324            paragraph_or_list -- Paragraph or Header element (or list)
325        """
326        for element in self.get_deleted():  # type: ignore
327            self.delete(element)  # type: ignore
328        if isinstance(paragraph_or_list, Element):
329            paragraph_or_list = [paragraph_or_list]
330        for element in paragraph_or_list:
331            self.append(element)

Set the deleted informations stored in the TextDeletion. An actual content that was deleted is expected, embeded in a Paragraph element or Header.

Arguments:

paragraph_or_list -- Paragraph or Header element (or list)
def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
333    def get_inserted(
334        self,
335        as_text: bool = False,
336        no_header: bool = False,
337        clean: bool = True,
338    ) -> str | Element | list[Element] | None:
339        """Return None."""
340        if as_text:
341            return ""
342        return None

Return None.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextInsertion
get_change_info
set_change_info
class TextFormatChange(odfdo.TextInsertion):
345class TextFormatChange(TextInsertion):
346    """The TextFormatChange "text:format-change" element represents any change
347    in formatting attributes. The region where the change took place is
348    marked by "text:change-start", "text:change-end" or "text:change"
349    elements.
350
351    Note: This element does not contain formatting changes that have taken
352    place.
353    """
354
355    _tag = "text:format-change"

The TextFormatChange "text:format-change" element represents any change in formatting attributes. The region where the change took place is marked by "text:change-start", "text:change-end" or "text:change" elements.

Note: This element does not contain formatting changes that have taken place.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextInsertion
get_deleted
get_inserted
get_change_info
set_change_info
class TextInsertion(odfdo.Element):
134class TextInsertion(Element):
135    """The TextInsertion "text:insertion" element contains the information
136    that identifies the person responsible for a change and the date of
137    that change. This information may also contain one or more "text:p"
138    Paragraph which contain a comment on the insertion. The
139    TextInsertion element's parent "text:changed-region" element has an
140    xml:id or text:id attribute, the value of which binds that parent
141    element to the text:change-id attribute on the "text:change-start"
142    and "text:change-end" elements.
143    """
144
145    _tag = "text:insertion"
146
147    def get_deleted(
148        self,
149        as_text: bool = False,
150        no_header: bool = False,
151    ) -> str | list[Element] | None:
152        """Return: None."""
153        if as_text:
154            return ""
155        return None
156
157    def get_inserted(
158        self,
159        as_text: bool = False,
160        no_header: bool = False,
161        clean: bool = True,
162    ) -> str | Element | list[Element] | None:
163        """Shortcut to text:change-start.get_inserted(). Return the content
164        between text:change-start and text:change-end.
165
166        If as_text is True: returns the text content.
167        If no_header is True: existing Heading are changed in Paragraph
168        If no_header is True: existing text:h are changed in text:p
169        By default: returns a list of Element, cleaned and with headers
170
171        Arguments:
172
173            as_text -- boolean
174
175            clean -- boolean
176
177            no_header -- boolean
178
179        Return: list or Element or text
180        """
181        current = self.parent  # text:changed-region
182        if not current:
183            raise ValueError
184        idx = current.get_id()  # type: ignore
185        body = self.document_body
186        if not body:
187            body = self.root
188        text_change = body.get_text_change_start(idx=idx)
189        if not text_change:
190            raise ValueError
191        return text_change.get_inserted(  # type: ignore
192            as_text=as_text, no_header=no_header, clean=clean
193        )
194
195    def get_change_info(self) -> Element | None:
196        """Get the ChangeInfo child of the element.
197
198        Return: ChangeInfo element.
199        """
200        return self.get_element("descendant::office:change-info")
201
202    def set_change_info(
203        self,
204        change_info: Element | None = None,
205        creator: str | None = None,
206        date: datetime | None = None,
207        comments: Element | list[Element] | None = None,
208    ) -> None:
209        """Set the ChangeInfo element for the change element. If change_info
210        is not provided, creator, date and comments will be used to build a
211        suitable change info element. Default for creator is 'Unknown',
212        default for date is current time and default for comments is no
213        comment at all.
214        The new change info element will replace any existant ChangeInfo.
215
216        Arguments:
217
218             change_info -- ChangeInfo element (or None)
219
220             cretor -- str (or None)
221
222             date -- datetime (or None)
223
224             comments -- Paragraph or list of Paragraph elements (or None)
225        """
226        if change_info is None:
227            new_change_info = ChangeInfo(creator, date)
228            if comments is not None:
229                if isinstance(comments, Element):
230                    # single pararagraph comment
231                    comments_list = [comments]
232                else:
233                    comments_list = comments
234                # assume iterable of Paragraph
235                for paragraph in comments_list:
236                    if not isinstance(paragraph, Paragraph):
237                        raise TypeError(f"Not a Paragraph: '{paragraph!r}'")
238                    new_change_info.insert(paragraph, xmlposition=LAST_CHILD)
239        else:
240            if not isinstance(change_info, ChangeInfo):
241                raise TypeError(f"Not a ChangeInfo: '{change_info!r}'")
242            new_change_info = change_info
243
244        old = self.get_change_info()
245        if old is not None:
246            self.replace_element(old, new_change_info)
247        else:
248            self.insert(new_change_info, xmlposition=FIRST_CHILD)

The TextInsertion "text:insertion" element contains the information that identifies the person responsible for a change and the date of that change. This information may also contain one or more "text:p" Paragraph which contain a comment on the insertion. The TextInsertion element's parent "text:changed-region" element has an xml:id or text:id attribute, the value of which binds that parent element to the text:change-id attribute on the "text:change-start" and "text:change-end" elements.

def get_deleted( self, as_text: bool = False, no_header: bool = False) -> str | list[Element] | None:
147    def get_deleted(
148        self,
149        as_text: bool = False,
150        no_header: bool = False,
151    ) -> str | list[Element] | None:
152        """Return: None."""
153        if as_text:
154            return ""
155        return None

Return: None.

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
157    def get_inserted(
158        self,
159        as_text: bool = False,
160        no_header: bool = False,
161        clean: bool = True,
162    ) -> str | Element | list[Element] | None:
163        """Shortcut to text:change-start.get_inserted(). Return the content
164        between text:change-start and text:change-end.
165
166        If as_text is True: returns the text content.
167        If no_header is True: existing Heading are changed in Paragraph
168        If no_header is True: existing text:h are changed in text:p
169        By default: returns a list of Element, cleaned and with headers
170
171        Arguments:
172
173            as_text -- boolean
174
175            clean -- boolean
176
177            no_header -- boolean
178
179        Return: list or Element or text
180        """
181        current = self.parent  # text:changed-region
182        if not current:
183            raise ValueError
184        idx = current.get_id()  # type: ignore
185        body = self.document_body
186        if not body:
187            body = self.root
188        text_change = body.get_text_change_start(idx=idx)
189        if not text_change:
190            raise ValueError
191        return text_change.get_inserted(  # type: ignore
192            as_text=as_text, no_header=no_header, clean=clean
193        )

Shortcut to text:change-start.get_inserted(). Return the content between text:change-start and text:change-end.

If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text

def get_change_info(self) -> Element | None:
195    def get_change_info(self) -> Element | None:
196        """Get the ChangeInfo child of the element.
197
198        Return: ChangeInfo element.
199        """
200        return self.get_element("descendant::office:change-info")

Get the ChangeInfo child of the element.

Return: ChangeInfo element.

def set_change_info( self, change_info: Element | None = None, creator: str | None = None, date: datetime.datetime | None = None, comments: Element | list[Element] | None = None) -> None:
202    def set_change_info(
203        self,
204        change_info: Element | None = None,
205        creator: str | None = None,
206        date: datetime | None = None,
207        comments: Element | list[Element] | None = None,
208    ) -> None:
209        """Set the ChangeInfo element for the change element. If change_info
210        is not provided, creator, date and comments will be used to build a
211        suitable change info element. Default for creator is 'Unknown',
212        default for date is current time and default for comments is no
213        comment at all.
214        The new change info element will replace any existant ChangeInfo.
215
216        Arguments:
217
218             change_info -- ChangeInfo element (or None)
219
220             cretor -- str (or None)
221
222             date -- datetime (or None)
223
224             comments -- Paragraph or list of Paragraph elements (or None)
225        """
226        if change_info is None:
227            new_change_info = ChangeInfo(creator, date)
228            if comments is not None:
229                if isinstance(comments, Element):
230                    # single pararagraph comment
231                    comments_list = [comments]
232                else:
233                    comments_list = comments
234                # assume iterable of Paragraph
235                for paragraph in comments_list:
236                    if not isinstance(paragraph, Paragraph):
237                        raise TypeError(f"Not a Paragraph: '{paragraph!r}'")
238                    new_change_info.insert(paragraph, xmlposition=LAST_CHILD)
239        else:
240            if not isinstance(change_info, ChangeInfo):
241                raise TypeError(f"Not a ChangeInfo: '{change_info!r}'")
242            new_change_info = change_info
243
244        old = self.get_change_info()
245        if old is not None:
246            self.replace_element(old, new_change_info)
247        else:
248            self.insert(new_change_info, xmlposition=FIRST_CHILD)

Set the ChangeInfo element for the change element. If change_info is not provided, creator, date and comments will be used to build a suitable change info element. Default for creator is 'Unknown', default for date is current time and default for comments is no comment at all. The new change info element will replace any existant ChangeInfo.

Arguments:

 change_info -- ChangeInfo element (or None)

 cretor -- str (or None)

 date -- datetime (or None)

 comments -- Paragraph or list of Paragraph elements (or None)
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TocEntryTemplate(odfdo.Element):
467class TocEntryTemplate(Element):
468    """ODF "text:table-of-content-entry-template"
469
470    Arguments:
471
472        style -- str
473    """
474
475    _tag = "text:table-of-content-entry-template"
476    _properties = (PropDef("style", "text:style-name"),)
477
478    def __init__(
479        self,
480        style: str | None = None,
481        outline_level: int | None = None,
482        **kwargs: Any,
483    ) -> None:
484        super().__init__(**kwargs)
485        if self._do_init:
486            if style:
487                self.style = style
488            if outline_level:
489                self.outline_level = outline_level
490
491    @property
492    def outline_level(self) -> int | None:
493        return self.get_attribute_integer("text:outline-level")
494
495    @outline_level.setter
496    def outline_level(self, level: int) -> None:
497        self.set_attribute("text:outline-level", str(level))
498
499    def complete_defaults(self) -> None:
500        self.append(Element.from_tag("text:index-entry-chapter"))
501        self.append(Element.from_tag("text:index-entry-text"))
502        self.append(Element.from_tag("text:index-entry-text"))
503        ts = Element.from_tag("text:index-entry-text")
504        ts.set_style_attribute("style:type", "right")
505        ts.set_style_attribute("style:leader-char", ".")
506        self.append(ts)
507        self.append(Element.from_tag("text:index-entry-page-number"))

ODF "text:table-of-content-entry-template"

Arguments:

style -- str
TocEntryTemplate( style: str | None = None, outline_level: int | None = None, **kwargs: Any)
478    def __init__(
479        self,
480        style: str | None = None,
481        outline_level: int | None = None,
482        **kwargs: Any,
483    ) -> None:
484        super().__init__(**kwargs)
485        if self._do_init:
486            if style:
487                self.style = style
488            if outline_level:
489                self.outline_level = outline_level
outline_level: int | None
491    @property
492    def outline_level(self) -> int | None:
493        return self.get_attribute_integer("text:outline-level")
def complete_defaults(self) -> None:
499    def complete_defaults(self) -> None:
500        self.append(Element.from_tag("text:index-entry-chapter"))
501        self.append(Element.from_tag("text:index-entry-text"))
502        self.append(Element.from_tag("text:index-entry-text"))
503        ts = Element.from_tag("text:index-entry-text")
504        ts.set_style_attribute("style:type", "right")
505        ts.set_style_attribute("style:leader-char", ".")
506        self.append(ts)
507        self.append(Element.from_tag("text:index-entry-page-number"))
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TrackedChanges(odfdo.Element):
452class TrackedChanges(Element):
453    """The TrackedChanges "text:tracked-changes" element acts as a container
454    for TextChangedRegion elements that represent changes in a certain
455    scope of an OpenDocument document. This scope is the element in which
456    the TrackedChanges element occurs. Changes in this scope shall be
457    tracked by TextChangedRegion elements contained in the
458    TrackedChanges element in this scope. If a TrackedChanges
459    element is absent, there are no tracked changes in the corresponding
460    scope. In this case, all change mark elements in this scope shall be
461    ignored.
462    """
463
464    _tag = "text:tracked-changes"
465
466    def get_changed_regions(
467        self,
468        creator: str | None = None,
469        date: datetime | None = None,
470        content: str | None = None,
471        role: str | None = None,
472    ) -> list[Element]:
473        changed_regions = self._filtered_elements(
474            "text:changed-region",
475            dc_creator=creator,
476            dc_date=date,
477            content=content,
478        )
479        if role is None:
480            return changed_regions
481        result: list[Element] = []
482        for regien in changed_regions:
483            changed = regien.get_change_element()  # type: ignore
484            if not changed:
485                continue
486            if changed.tag.endswith(role):
487                result.append(regien)
488        return result
489
490    def get_changed_region(
491        self,
492        position: int = 0,
493        text_id: str | None = None,
494        creator: str | None = None,
495        date: datetime | None = None,
496        content: str | None = None,
497    ) -> Element | None:
498        return self._filtered_element(
499            "text:changed-region",
500            position,
501            text_id=text_id,
502            dc_creator=creator,
503            dc_date=date,
504            content=content,
505        )

The TrackedChanges "text:tracked-changes" element acts as a container for TextChangedRegion elements that represent changes in a certain scope of an OpenDocument document. This scope is the element in which the TrackedChanges element occurs. Changes in this scope shall be tracked by TextChangedRegion elements contained in the TrackedChanges element in this scope. If a TrackedChanges element is absent, there are no tracked changes in the corresponding scope. In this case, all change mark elements in this scope shall be ignored.

def get_changed_regions( self, creator: str | None = None, date: datetime.datetime | None = None, content: str | None = None, role: str | None = None) -> list[Element]:
466    def get_changed_regions(
467        self,
468        creator: str | None = None,
469        date: datetime | None = None,
470        content: str | None = None,
471        role: str | None = None,
472    ) -> list[Element]:
473        changed_regions = self._filtered_elements(
474            "text:changed-region",
475            dc_creator=creator,
476            dc_date=date,
477            content=content,
478        )
479        if role is None:
480            return changed_regions
481        result: list[Element] = []
482        for regien in changed_regions:
483            changed = regien.get_change_element()  # type: ignore
484            if not changed:
485                continue
486            if changed.tag.endswith(role):
487                result.append(regien)
488        return result
def get_changed_region( self, position: int = 0, text_id: str | None = None, creator: str | None = None, date: datetime.datetime | None = None, content: str | None = None) -> Element | None:
490    def get_changed_region(
491        self,
492        position: int = 0,
493        text_id: str | None = None,
494        creator: str | None = None,
495        date: datetime | None = None,
496        content: str | None = None,
497    ) -> Element | None:
498        return self._filtered_element(
499            "text:changed-region",
500            position,
501            text_id=text_id,
502            dc_creator=creator,
503            dc_date=date,
504            content=content,
505        )
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserDefined(odfdo.ElementTyped):
212class UserDefined(ElementTyped):
213    """Return a user defined field "text:user-defined". If the current
214    document is provided, try to extract the content of the meta user defined
215    field of same name.
216
217    Arguments:
218
219        name -- str, name of the user defined field
220
221        value -- python typed value, value of the field
222
223        value_type -- str, office:value-type known type
224
225        text -- str
226
227        style -- str
228
229        from_document -- ODF document
230    """
231
232    _tag = "text:user-defined"
233    _properties = (
234        PropDef("name", "text:name"),
235        PropDef("style", "style:data-style-name"),
236    )
237
238    def __init__(
239        self,
240        name: str = "",
241        value: Any = None,
242        value_type: str | None = None,
243        text: str | None = None,
244        style: str | None = None,
245        from_document: Document | None = None,
246        **kwargs: Any,
247    ) -> None:
248        super().__init__(**kwargs)
249        if self._do_init:
250            if name:
251                self.name = name
252            if style:
253                self.style = style
254            if from_document is not None:
255                meta_infos = from_document.meta
256                content = meta_infos.get_user_defined_metadata_of_name(name)
257                if content is not None:
258                    value = content.get("value", None)
259                    value_type = content.get("value_type", None)
260                    text = content.get("text", None)
261            text = self.set_value_and_type(
262                value=value, value_type=value_type, text=text
263            )
264            self.text = text  # type: ignore

Return a user defined field "text:user-defined". If the current document is provided, try to extract the content of the meta user defined field of same name.

Arguments:

name -- str, name of the user defined field

value -- python typed value, value of the field

value_type -- str, office:value-type known type

text -- str

style -- str

from_document -- ODF document
UserDefined( name: str = '', value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, from_document: Document | None = None, **kwargs: Any)
238    def __init__(
239        self,
240        name: str = "",
241        value: Any = None,
242        value_type: str | None = None,
243        text: str | None = None,
244        style: str | None = None,
245        from_document: Document | None = None,
246        **kwargs: Any,
247    ) -> None:
248        super().__init__(**kwargs)
249        if self._do_init:
250            if name:
251                self.name = name
252            if style:
253                self.style = style
254            if from_document is not None:
255                meta_infos = from_document.meta
256                content = meta_infos.get_user_defined_metadata_of_name(name)
257                if content is not None:
258                    value = content.get("value", None)
259                    value_type = content.get("value_type", None)
260                    text = content.get("text", None)
261            text = self.set_value_and_type(
262                value=value, value_type=value_type, text=text
263            )
264            self.text = text  # type: ignore
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldDecl(odfdo.ElementTyped):
146class UserFieldDecl(ElementTyped):
147    _tag = "text:user-field-decl"
148    _properties = (PropDef("name", "text:name"),)
149
150    def __init__(
151        self,
152        name: str | None = None,
153        value: Any = None,
154        value_type: str | None = None,
155        **kwargs: Any,
156    ) -> None:
157        super().__init__(**kwargs)
158        if self._do_init:
159            if name:
160                self.name = name
161            self.set_value_and_type(value=value, value_type=value_type)
162
163    def set_value(self, value: Any) -> None:
164        name = self.get_attribute("text:name")
165        self.clear()
166        self.set_value_and_type(value=value)
167        self.set_attribute("text:name", name)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

UserFieldDecl( name: str | None = None, value: Any = None, value_type: str | None = None, **kwargs: Any)
150    def __init__(
151        self,
152        name: str | None = None,
153        value: Any = None,
154        value_type: str | None = None,
155        **kwargs: Any,
156    ) -> None:
157        super().__init__(**kwargs)
158        if self._do_init:
159            if name:
160                self.name = name
161            self.set_value_and_type(value=value, value_type=value_type)
def set_value(self, value: Any) -> None:
163    def set_value(self, value: Any) -> None:
164        name = self.get_attribute("text:name")
165        self.clear()
166        self.set_value_and_type(value=value)
167        self.set_attribute("text:name", name)
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldDecls(odfdo.Element):
142class UserFieldDecls(Element):
143    _tag = "text:user-field-decls"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldGet(odfdo.ElementTyped):
173class UserFieldGet(ElementTyped):
174    _tag = "text:user-field-get"
175    _properties = (
176        PropDef("name", "text:name"),
177        PropDef("style", "style:data-style-name"),
178    )
179
180    def __init__(
181        self,
182        name: str | None = None,
183        value: Any = None,
184        value_type: str | None = None,
185        text: str | None = None,
186        style: str | None = None,
187        **kwargs: Any,
188    ) -> None:
189        super().__init__(**kwargs)
190        if self._do_init:
191            if name:
192                self.name = name
193            text = self.set_value_and_type(
194                value=value, value_type=value_type, text=text
195            )
196            self.text = text  # type: ignore
197
198            if style:
199                self.style = style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

UserFieldGet( name: str | None = None, value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, **kwargs: Any)
180    def __init__(
181        self,
182        name: str | None = None,
183        value: Any = None,
184        value_type: str | None = None,
185        text: str | None = None,
186        style: str | None = None,
187        **kwargs: Any,
188    ) -> None:
189        super().__init__(**kwargs)
190        if self._do_init:
191            if name:
192                self.name = name
193            text = self.set_value_and_type(
194                value=value, value_type=value_type, text=text
195            )
196            self.text = text  # type: ignore
197
198            if style:
199                self.style = style
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldInput(odfdo.UserFieldGet):
205class UserFieldInput(UserFieldGet):
206    _tag = "text:user-field-input"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
UserFieldGet
UserFieldGet
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarChapter(odfdo.Element):
376class VarChapter(Element):
377    _tag = "text:chapter"
378    _properties = (
379        PropDef("display", "text:display"),
380        PropDef("outline_level", "text:outline-level"),
381    )
382    DISPLAY_VALUE_CHOICE = {  # noqa: RUF012
383        "number",
384        "name",
385        "number-and-name",
386        "plain-number",
387        "plain-number-and-name",
388    }
389
390    def __init__(
391        self,
392        display: str | None = "name",
393        outline_level: str | None = None,
394        **kwargs: Any,
395    ) -> None:
396        """display can be: 'number', 'name', 'number-and-name', 'plain-number' or
397        'plain-number-and-name'
398        """
399        super().__init__(**kwargs)
400        if self._do_init:
401            if display not in VarChapter.DISPLAY_VALUE_CHOICE:
402                raise ValueError("unknown display value: %s" % display)
403            self.display = display
404            if outline_level is not None:
405                self.outline_level = outline_level

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarChapter( display: str | None = 'name', outline_level: str | None = None, **kwargs: Any)
390    def __init__(
391        self,
392        display: str | None = "name",
393        outline_level: str | None = None,
394        **kwargs: Any,
395    ) -> None:
396        """display can be: 'number', 'name', 'number-and-name', 'plain-number' or
397        'plain-number-and-name'
398        """
399        super().__init__(**kwargs)
400        if self._do_init:
401            if display not in VarChapter.DISPLAY_VALUE_CHOICE:
402                raise ValueError("unknown display value: %s" % display)
403            self.display = display
404            if outline_level is not None:
405                self.outline_level = outline_level

display can be: 'number', 'name', 'number-and-name', 'plain-number' or 'plain-number-and-name'

DISPLAY_VALUE_CHOICE = {'name', 'plain-number-and-name', 'plain-number', 'number', 'number-and-name'}
display: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
outline_level: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarCreationDate(odfdo.Element):
456class VarCreationDate(Element):
457    _tag = "text:creation-date"
458    _properties = (
459        PropDef("fixed", "text:fixed"),
460        PropDef("data_style", "style:data-style-name"),
461    )
462
463    def __init__(
464        self,
465        fixed: bool = False,
466        data_style: str | None = None,
467        **kwargs: Any,
468    ) -> None:
469        super().__init__(**kwargs)
470        if self._do_init:
471            if fixed:
472                self.fixed = True
473            if data_style:
474                self.data_style = data_style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarCreationDate(fixed: bool = False, data_style: str | None = None, **kwargs: Any)
463    def __init__(
464        self,
465        fixed: bool = False,
466        data_style: str | None = None,
467        **kwargs: Any,
468    ) -> None:
469        super().__init__(**kwargs)
470        if self._do_init:
471            if fixed:
472                self.fixed = True
473            if data_style:
474                self.data_style = data_style
fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
data_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarCreationTime(odfdo.Element):
480class VarCreationTime(Element):
481    _tag = "text:creation-time"
482    _properties = (
483        PropDef("fixed", "text:fixed"),
484        PropDef("data_style", "style:data-style-name"),
485    )
486
487    def __init__(
488        self,
489        fixed: bool = False,
490        data_style: str | None = None,
491        **kwargs: Any,
492    ) -> None:
493        super().__init__(**kwargs)
494        if self._do_init:
495            if fixed:
496                self.fixed = True
497            if data_style:
498                self.data_style = data_style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarCreationTime(fixed: bool = False, data_style: str | None = None, **kwargs: Any)
487    def __init__(
488        self,
489        fixed: bool = False,
490        data_style: str | None = None,
491        **kwargs: Any,
492    ) -> None:
493        super().__init__(**kwargs)
494        if self._do_init:
495            if fixed:
496                self.fixed = True
497            if data_style:
498                self.data_style = data_style
fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
data_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDate(odfdo.Element):
305class VarDate(Element):
306    _tag = "text:date"
307    _properties = (
308        PropDef("date", "text:date-value"),
309        PropDef("fixed", "text:fixed"),
310        PropDef("data_style", "style:data-style-name"),
311        PropDef("date_adjust", "text:date-adjust"),
312    )
313
314    def __init__(
315        self,
316        date: datetime | None = None,
317        fixed: bool = False,
318        data_style: str | None = None,
319        text: str | None = None,
320        date_adjust: timedelta | None = None,
321        **kwargs: Any,
322    ) -> None:
323        super().__init__(**kwargs)
324        if self._do_init:
325            if date:
326                self.date = DateTime.encode(date)
327            if fixed:
328                self.fixed = True
329            if data_style is not None:
330                self.data_style = data_style
331            if text is None and date is not None:
332                text = Date.encode(date)
333            self.text = text  # type: ignore
334            if date_adjust is not None:
335                self.date_adjust = Duration.encode(date_adjust)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarDate( date: datetime.datetime | None = None, fixed: bool = False, data_style: str | None = None, text: str | None = None, date_adjust: datetime.timedelta | None = None, **kwargs: Any)
314    def __init__(
315        self,
316        date: datetime | None = None,
317        fixed: bool = False,
318        data_style: str | None = None,
319        text: str | None = None,
320        date_adjust: timedelta | None = None,
321        **kwargs: Any,
322    ) -> None:
323        super().__init__(**kwargs)
324        if self._do_init:
325            if date:
326                self.date = DateTime.encode(date)
327            if fixed:
328                self.fixed = True
329            if data_style is not None:
330                self.data_style = data_style
331            if text is None and date is not None:
332                text = Date.encode(date)
333            self.text = text  # type: ignore
334            if date_adjust is not None:
335                self.date_adjust = Duration.encode(date_adjust)
date: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
data_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
date_adjust: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDecl(odfdo.Element):
40class VarDecl(Element):
41    _tag = "text:variable-decl"
42    _properties = (
43        PropDef("name", "text:name"),
44        PropDef("value_type", "office:value-type"),
45    )
46
47    def __init__(
48        self,
49        name: str | None = None,
50        value_type: str | None = None,
51        **kwargs: Any,
52    ) -> None:
53        super().__init__(**kwargs)
54        if self._do_init:
55            if name:
56                self.name = name
57            if value_type:
58                self.value_type = value_type

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarDecl( name: str | None = None, value_type: str | None = None, **kwargs: Any)
47    def __init__(
48        self,
49        name: str | None = None,
50        value_type: str | None = None,
51        **kwargs: Any,
52    ) -> None:
53        super().__init__(**kwargs)
54        if self._do_init:
55            if name:
56                self.name = name
57            if value_type:
58                self.value_type = value_type
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
value_type: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDecls(odfdo.Element):
36class VarDecls(Element):
37    _tag = "text:variable-decls"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDescription(odfdo.VarInitialCreator):
504class VarDescription(VarInitialCreator):
505    _tag = "text:description"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarFileName(odfdo.Element):
411class VarFileName(Element):
412    _tag = "text:file-name"
413    _properties = (
414        PropDef("display", "text:display"),
415        PropDef("fixed", "text:fixed"),
416    )
417    DISPLAY_VALUE_CHOICE = {  # noqa: RUF012
418        "full",
419        "path",
420        "name",
421        "name-and-extension",
422    }
423
424    def __init__(
425        self,
426        display: str | None = "full",
427        fixed: bool = False,
428        **kwargs: Any,
429    ) -> None:
430        """display can be: 'full', 'path', 'name' or 'name-and-extension'"""
431        super().__init__(**kwargs)
432        if self._do_init:
433            if display not in VarFileName.DISPLAY_VALUE_CHOICE:
434                raise ValueError("unknown display value: %s" % display)
435            self.display = display
436            if fixed:
437                self.fixed = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarFileName(display: str | None = 'full', fixed: bool = False, **kwargs: Any)
424    def __init__(
425        self,
426        display: str | None = "full",
427        fixed: bool = False,
428        **kwargs: Any,
429    ) -> None:
430        """display can be: 'full', 'path', 'name' or 'name-and-extension'"""
431        super().__init__(**kwargs)
432        if self._do_init:
433            if display not in VarFileName.DISPLAY_VALUE_CHOICE:
434                raise ValueError("unknown display value: %s" % display)
435            self.display = display
436            if fixed:
437                self.fixed = True

display can be: 'full', 'path', 'name' or 'name-and-extension'

DISPLAY_VALUE_CHOICE = {'name', 'name-and-extension', 'full', 'path'}
display: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarGet(odfdo.ElementTyped):
111class VarGet(ElementTyped):
112    _tag = "text:variable-get"
113    _properties = (
114        PropDef("name", "text:name"),
115        PropDef("style", "style:data-style-name"),
116    )
117
118    def __init__(
119        self,
120        name: str | None = None,
121        value: Any = None,
122        value_type: str | None = None,
123        text: str | None = None,
124        style: str | None = None,
125        **kwargs: Any,
126    ) -> None:
127        super().__init__(**kwargs)
128        if self._do_init:
129            if name:
130                self.name = name
131            if style:
132                self.style = style
133            text = self.set_value_and_type(
134                value=value, value_type=value_type, text=text
135            )
136            self.text = text  # type: ignore

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarGet( name: str | None = None, value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, **kwargs: Any)
118    def __init__(
119        self,
120        name: str | None = None,
121        value: Any = None,
122        value_type: str | None = None,
123        text: str | None = None,
124        style: str | None = None,
125        **kwargs: Any,
126    ) -> None:
127        super().__init__(**kwargs)
128        if self._do_init:
129            if name:
130                self.name = name
131            if style:
132                self.style = style
133            text = self.set_value_and_type(
134                value=value, value_type=value_type, text=text
135            )
136            self.text = text  # type: ignore
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarInitialCreator(odfdo.Element):
443class VarInitialCreator(Element):
444    _tag = "text:initial-creator"
445    _properties = (PropDef("fixed", "text:fixed"),)
446
447    def __init__(self, fixed: bool = False, **kwargs: Any) -> None:
448        super().__init__(**kwargs)
449        if self._do_init and fixed:
450            self.fixed = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarInitialCreator(fixed: bool = False, **kwargs: Any)
447    def __init__(self, fixed: bool = False, **kwargs: Any) -> None:
448        super().__init__(**kwargs)
449        if self._do_init and fixed:
450            self.fixed = True
fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarKeywords(odfdo.VarInitialCreator):
525class VarKeywords(VarInitialCreator):
526    _tag = "text:keywords"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarPageCount(odfdo.Element):
301class VarPageCount(Element):
302    _tag = "text:page-count"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarPageNumber(odfdo.Element):
270class VarPageNumber(Element):
271    """
272    select_page -- string in ('previous', 'current', 'next')
273
274    page_adjust -- int (to add or subtract to the page number)
275    """
276
277    _tag = "text:page-number"
278    _properties = (
279        PropDef("select_page", "text:select-page"),
280        PropDef("page_adjust", "text:page-adjust"),
281    )
282
283    def __init__(
284        self,
285        select_page: str | None = None,
286        page_adjust: str | None = None,
287        **kwargs: Any,
288    ) -> None:
289        super().__init__(**kwargs)
290        if self._do_init:
291            if select_page is None:
292                select_page = "current"
293            self.select_page = select_page
294            if page_adjust is not None:
295                self.page_adjust = page_adjust

select_page -- string in ('previous', 'current', 'next')

page_adjust -- int (to add or subtract to the page number)

VarPageNumber( select_page: str | None = None, page_adjust: str | None = None, **kwargs: Any)
283    def __init__(
284        self,
285        select_page: str | None = None,
286        page_adjust: str | None = None,
287        **kwargs: Any,
288    ) -> None:
289        super().__init__(**kwargs)
290        if self._do_init:
291            if select_page is None:
292                select_page = "current"
293            self.select_page = select_page
294            if page_adjust is not None:
295                self.page_adjust = page_adjust
select_page: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
page_adjust: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarSet(odfdo.ElementTyped):
 64class VarSet(ElementTyped):
 65    _tag = "text:variable-set"
 66    _properties = (
 67        PropDef("name", "text:name"),
 68        PropDef("style", "style:data-style-name"),
 69        PropDef("display", "text:display"),
 70    )
 71
 72    def __init__(
 73        self,
 74        name: str | None = None,
 75        value: Any = None,
 76        value_type: str | None = None,
 77        display: str | bool = False,
 78        text: str | None = None,
 79        style: str | None = None,
 80        **kwargs: Any,
 81    ) -> None:
 82        super().__init__(**kwargs)
 83        if self._do_init:
 84            if name:
 85                self.name = name
 86            if style:
 87                self.style = style
 88            text = self.set_value_and_type(
 89                value=value, value_type=value_type, text=text
 90            )
 91            if not display:
 92                self.display = "none"
 93            else:
 94                self.text = text  # type: ignore
 95
 96    def set_value(self, value: Any) -> None:
 97        name = self.get_attribute("text:name")
 98        display = self.get_attribute("text:display")
 99        self.clear()
100        text = self.set_value_and_type(value=value)
101        self.set_attribute("text:name", name)
102        if display is not None:
103            self.set_attribute("text:display", display)
104        if isinstance(text, str):
105            self.text = text

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarSet( name: str | None = None, value: Any = None, value_type: str | None = None, display: str | bool = False, text: str | None = None, style: str | None = None, **kwargs: Any)
72    def __init__(
73        self,
74        name: str | None = None,
75        value: Any = None,
76        value_type: str | None = None,
77        display: str | bool = False,
78        text: str | None = None,
79        style: str | None = None,
80        **kwargs: Any,
81    ) -> None:
82        super().__init__(**kwargs)
83        if self._do_init:
84            if name:
85                self.name = name
86            if style:
87                self.style = style
88            text = self.set_value_and_type(
89                value=value, value_type=value_type, text=text
90            )
91            if not display:
92                self.display = "none"
93            else:
94                self.text = text  # type: ignore
def set_value(self, value: Any) -> None:
 96    def set_value(self, value: Any) -> None:
 97        name = self.get_attribute("text:name")
 98        display = self.get_attribute("text:display")
 99        self.clear()
100        text = self.set_value_and_type(value=value)
101        self.set_attribute("text:name", name)
102        if display is not None:
103            self.set_attribute("text:display", display)
104        if isinstance(text, str):
105            self.text = text
name: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
display: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarSubject(odfdo.VarInitialCreator):
518class VarSubject(VarInitialCreator):
519    _tag = "text:subject"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarTime(odfdo.Element):
341class VarTime(Element):
342    _tag = "text:time"
343    _properties = (
344        PropDef("time", "text:time-value"),
345        PropDef("fixed", "text:fixed"),
346        PropDef("data_style", "style:data-style-name"),
347        PropDef("time_adjust", "text:time-adjust"),
348    )
349
350    def __init__(
351        self,
352        time: datetime,
353        fixed: bool = False,
354        data_style: str | None = None,
355        text: str | None = None,
356        time_adjust: timedelta | None = None,
357        **kwargs: Any,
358    ) -> None:
359        super().__init__(**kwargs)
360        if self._do_init:
361            self.time = DateTime.encode(time)
362            if fixed:
363                self.fixed = True
364            if data_style is not None:
365                self.data_style = data_style
366            if text is None:
367                text = time.strftime("%H:%M:%S")
368            self.text = text
369            if time_adjust is not None:
370                self.date_adjust = Duration.encode(time_adjust)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarTime( time: datetime.datetime, fixed: bool = False, data_style: str | None = None, text: str | None = None, time_adjust: datetime.timedelta | None = None, **kwargs: Any)
350    def __init__(
351        self,
352        time: datetime,
353        fixed: bool = False,
354        data_style: str | None = None,
355        text: str | None = None,
356        time_adjust: timedelta | None = None,
357        **kwargs: Any,
358    ) -> None:
359        super().__init__(**kwargs)
360        if self._do_init:
361            self.time = DateTime.encode(time)
362            if fixed:
363                self.fixed = True
364            if data_style is not None:
365                self.data_style = data_style
366            if text is None:
367                text = time.strftime("%H:%M:%S")
368            self.text = text
369            if time_adjust is not None:
370                self.date_adjust = Duration.encode(time_adjust)
time: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
data_style: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
time_adjust: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarTitle(odfdo.VarInitialCreator):
511class VarTitle(VarInitialCreator):
512    _tag = "text:title"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
394        def getter(self: Element) -> str | bool | None:
395            try:
396                if family and self.family != family:  # type: ignore
397                    return None
398            except AttributeError:
399                return None
400            value = self.__element.get(name)
401            if value is None:
402                return None
403            elif value in ("true", "false"):
404                return Boolean.decode(value)
405            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class XmlPart:
 37class XmlPart:
 38    """Representation of an XML part.
 39
 40    Abstraction of the XML library behind.
 41    """
 42
 43    def __init__(self, part_name: str, container: Container) -> None:
 44        self.part_name = part_name
 45        self.container = container
 46
 47        # Internal state
 48        self.__tree: _ElementTree | None = None
 49        self.__root: Element | None = None
 50
 51    def _get_tree(self) -> _ElementTree:
 52        if self.__tree is None:
 53            part = self.container.get_part(self.part_name)
 54            self.__tree = parse(BytesIO(part))  # type: ignore
 55        return self.__tree
 56
 57    def __repr__(self) -> str:
 58        return f"<{self.__class__.__name__} part_name={self.part_name}>"
 59
 60    # Public API
 61
 62    @property
 63    def root(self) -> Element:
 64        if self.__root is None:
 65            tree = self._get_tree()
 66            self.__root = Element.from_tag(tree.getroot())
 67        return self.__root
 68
 69    def get_elements(self, xpath_query: str) -> list[Element | Text]:
 70        root = self.root
 71        return root.xpath(xpath_query)
 72
 73    def get_element(self, xpath_query: str) -> Any:
 74        result = self.get_elements(xpath_query)
 75        if not result:
 76            return None
 77        return result[0]
 78
 79    def delete_element(self, child: Element) -> None:
 80        child.delete()
 81
 82    def xpath(self, xpath_query: str) -> list[Element | Text]:
 83        """Apply XPath query to the XML part. Return list of Element or
 84        Text instances translated from the nodes found.
 85        """
 86        root = self.root
 87        return root.xpath(xpath_query)
 88
 89    @property
 90    def clone(self) -> XmlPart:
 91        clone = object.__new__(self.__class__)
 92        for name in self.__dict__:
 93            if name == "container":
 94                setattr(clone, name, self.container.clone)
 95            elif name in ("_XmlPart__tree",):
 96                setattr(clone, name, None)
 97            else:
 98                value = getattr(self, name)
 99                value = deepcopy(value)
100                setattr(clone, name, value)
101        return clone
102
103    def serialize(self, pretty: bool = False) -> bytes:
104        tree = self._get_tree()
105        # Lxml declaration is too exotic to me
106        data = [b'<?xml version="1.0" encoding="UTF-8"?>']
107        bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8")
108        # Lxml with pretty_print is adding a empty line
109        if pretty:
110            bytes_tree = bytes_tree.strip()
111        data.append(bytes_tree)
112        return b"\n".join(data)

Representation of an XML part.

Abstraction of the XML library behind.

XmlPart(part_name: str, container: Container)
43    def __init__(self, part_name: str, container: Container) -> None:
44        self.part_name = part_name
45        self.container = container
46
47        # Internal state
48        self.__tree: _ElementTree | None = None
49        self.__root: Element | None = None
part_name
container
root: Element
62    @property
63    def root(self) -> Element:
64        if self.__root is None:
65            tree = self._get_tree()
66            self.__root = Element.from_tag(tree.getroot())
67        return self.__root
def get_elements( self, xpath_query: str) -> list[Element | Text]:
69    def get_elements(self, xpath_query: str) -> list[Element | Text]:
70        root = self.root
71        return root.xpath(xpath_query)
def get_element(self, xpath_query: str) -> Any:
73    def get_element(self, xpath_query: str) -> Any:
74        result = self.get_elements(xpath_query)
75        if not result:
76            return None
77        return result[0]
def delete_element(self, child: Element) -> None:
79    def delete_element(self, child: Element) -> None:
80        child.delete()
def xpath( self, xpath_query: str) -> list[Element | Text]:
82    def xpath(self, xpath_query: str) -> list[Element | Text]:
83        """Apply XPath query to the XML part. Return list of Element or
84        Text instances translated from the nodes found.
85        """
86        root = self.root
87        return root.xpath(xpath_query)

Apply XPath query to the XML part. Return list of Element or Text instances translated from the nodes found.

clone: XmlPart
 89    @property
 90    def clone(self) -> XmlPart:
 91        clone = object.__new__(self.__class__)
 92        for name in self.__dict__:
 93            if name == "container":
 94                setattr(clone, name, self.container.clone)
 95            elif name in ("_XmlPart__tree",):
 96                setattr(clone, name, None)
 97            else:
 98                value = getattr(self, name)
 99                value = deepcopy(value)
100                setattr(clone, name, value)
101        return clone
def serialize(self, pretty: bool = False) -> bytes:
103    def serialize(self, pretty: bool = False) -> bytes:
104        tree = self._get_tree()
105        # Lxml declaration is too exotic to me
106        data = [b'<?xml version="1.0" encoding="UTF-8"?>']
107        bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8")
108        # Lxml with pretty_print is adding a empty line
109        if pretty:
110            bytes_tree = bytes_tree.strip()
111        data.append(bytes_tree)
112        return b"\n".join(data)
__version__ = '3.7.1'
def create_table_cell_style( border: str | None = None, border_top: str | None = None, border_bottom: str | None = None, border_left: str | None = None, border_right: str | None = None, padding: str | None = None, padding_top: str | None = None, padding_bottom: str | None = None, padding_left: str | None = None, padding_right: str | None = None, background_color: str | tuple | None = None, shadow: str | None = None, color: str | tuple | None = None) -> Style:
236def create_table_cell_style(
237    border: str | None = None,
238    border_top: str | None = None,
239    border_bottom: str | None = None,
240    border_left: str | None = None,
241    border_right: str | None = None,
242    padding: str | None = None,
243    padding_top: str | None = None,
244    padding_bottom: str | None = None,
245    padding_left: str | None = None,
246    padding_right: str | None = None,
247    background_color: str | tuple | None = None,
248    shadow: str | None = None,
249    color: str | tuple | None = None,
250) -> Style:
251    """Return a cell style.
252
253    The borders arguments must be some style attribute strings or None, see the
254    method 'make_table_cell_border_string' to generate them.
255    If the 'border' argument as the value 'default', the default style
256    "0.06pt solid #000000" is used for the 4 borders.
257    If any value is used for border, it is used for the 4 borders, else any of
258    the 4 borders can be specified by it's own string. If all the border,
259    border_top, border_bottom, ... arguments are None, an empty border is used
260    (ODF value is fo:border="none").
261
262    Padding arguments are string specifying a length (e.g. "0.5mm")". If
263    'padding' is provided, it is used for the 4 sides, else any of
264    the 4 sides padding can be specified by it's own string. Default padding is
265    no padding.
266
267    Arguments:
268
269        border -- str, style string for borders on four sides
270
271        border_top -- str, style string for top if no 'border' argument
272
273        border_bottom -- str, style string for bottom if no 'border' argument
274
275        border_left -- str, style string for left if no 'border' argument
276
277        border_right -- str, style string for right if no 'border' argument
278
279        padding -- str, style string for padding on four sides
280
281        padding_top -- str, style string for top if no 'padding' argument
282
283        padding_bottom -- str, style string for bottom if no 'padding' argument
284
285        padding_left -- str, style string for left if no 'padding' argument
286
287        padding_right -- str, style string for right if no 'padding' argument
288
289        background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
290
291        shadow -- str, e.g. "#808080 0.176cm 0.176cm"
292
293        color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
294
295    Return : Style
296    """
297    if border == "default":
298        border = make_table_cell_border_string()  # default border
299    if border is not None:
300        # use the border value for 4 sides.
301        border_bottom = border_top = border_left = border_right = None
302    if (
303        border is None
304        and border_bottom is None
305        and border_top is None
306        and border_left is None
307        and border_right is None
308    ):
309        border = "none"
310    if padding is not None:
311        # use the padding value for 4 sides.
312        padding_bottom = padding_top = padding_left = padding_right = None
313    if color:
314        color_string = _make_color_string(color)
315    if background_color:
316        bgcolor_string = _make_color_string(background_color)
317    else:
318        bgcolor_string = None
319    cell_style = Style(
320        "table-cell",
321        area="table-cell",
322        border=border,
323        border_top=border_top,
324        border_bottom=border_bottom,
325        border_left=border_left,
326        border_right=border_right,
327        padding=padding,
328        padding_top=padding_top,
329        padding_bottom=padding_bottom,
330        padding_left=padding_left,
331        padding_right=padding_right,
332        background_color=bgcolor_string,
333        shadow=shadow,
334    )
335    if color:
336        cell_style.set_properties(area="text", color=color_string)
337    return cell_style

Return a cell style.

The borders arguments must be some style attribute strings or None, see the method 'make_table_cell_border_string' to generate them. If the 'border' argument as the value 'default', the default style "0.06pt solid #000000" is used for the 4 borders. If any value is used for border, it is used for the 4 borders, else any of the 4 borders can be specified by it's own string. If all the border, border_top, border_bottom, ... arguments are None, an empty border is used (ODF value is fo:border="none").

Padding arguments are string specifying a length (e.g. "0.5mm")". If 'padding' is provided, it is used for the 4 sides, else any of the 4 sides padding can be specified by it's own string. Default padding is no padding.

Arguments:

border -- str, style string for borders on four sides

border_top -- str, style string for top if no 'border' argument

border_bottom -- str, style string for bottom if no 'border' argument

border_left -- str, style string for left if no 'border' argument

border_right -- str, style string for right if no 'border' argument

padding -- str, style string for padding on four sides

padding_top -- str, style string for top if no 'padding' argument

padding_bottom -- str, style string for bottom if no 'padding' argument

padding_left -- str, style string for left if no 'padding' argument

padding_right -- str, style string for right if no 'padding' argument

background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

Return : Style

def default_boolean_style() -> Element:
1155def default_boolean_style() -> Element:
1156    return Element.from_tag(
1157        """<number:boolean-style style:name="lpod-default-boolean-style">
1158           <number:boolean/>
1159           </number:boolean-style>"""
1160    )
def default_currency_style() -> Element:
1163def default_currency_style() -> Element:
1164    return Element.from_tag(
1165        """<number:currency-style style:name="lpod-default-currency-style">
1166            <number:text>-</number:text>
1167            <number:number number:decimal-places="2"
1168             number:min-integer-digits="1"
1169             number:grouping="true"/>
1170            <number:text> </number:text>
1171            <number:currency-symbol
1172             number:language="fr"
1173             number:country="FR">€</number:currency-symbol>
1174           </number:currency-style>"""
1175    )
def default_date_style() -> Element:
1142def default_date_style() -> Element:
1143    return Element.from_tag(
1144        """
1145           <number:date-style style:name="lpod-default-date-style">
1146           <number:year number:style="long"/>
1147           <number:text>-</number:text>
1148           <number:month number:style="long"/>
1149           <number:text>-</number:text>
1150           <number:day number:style="long"/>
1151           </number:date-style>"""
1152    )
def default_frame_position_style( name: str = 'FramePosition', horizontal_pos: str = 'from-left', vertical_pos: str = 'from-top', horizontal_rel: str = 'paragraph', vertical_rel: str = 'paragraph') -> Style:
42def default_frame_position_style(
43    name: str = "FramePosition",
44    horizontal_pos: str = "from-left",
45    vertical_pos: str = "from-top",
46    horizontal_rel: str = "paragraph",
47    vertical_rel: str = "paragraph",
48) -> Style:
49    """Helper style for positioning frames in desktop applications that need
50    it.
51
52    Default arguments should be enough.
53
54    Use the returned Style as the frame style or build a new graphic style
55    with this style as the parent.
56    """
57    return Style(
58        family="graphic",
59        name=name,
60        horizontal_pos=horizontal_pos,
61        horizontal_rel=horizontal_rel,
62        vertical_pos=vertical_pos,
63        vertical_rel=vertical_rel,
64    )

Helper style for positioning frames in desktop applications that need it.

Default arguments should be enough.

Use the returned Style as the frame style or build a new graphic style with this style as the parent.

def default_number_style() -> Element:
1110def default_number_style() -> Element:
1111    return Element.from_tag(
1112        """<number:number-style style:name="lpod-default-number-style">
1113           <number:number number:decimal-places="2"
1114            number:min-integer-digits="1"/>
1115           </number:number-style>"""
1116    )
def default_percentage_style() -> Element:
1119def default_percentage_style() -> Element:
1120    return Element.from_tag(
1121        """<number:percentage-style
1122            style:name="lpod-default-percentage-style">
1123           <number:number number:decimal-places="2"
1124            number:min-integer-digits="1"/>
1125           <number:text>%</number:text>
1126           </number:percentage-style>"""
1127    )
def default_time_style() -> Element:
1130def default_time_style() -> Element:
1131    return Element.from_tag(
1132        """<number:time-style style:name="lpod-default-time-style">
1133           <number:hours number:style="long"/>
1134           <number:text>:</number:text>
1135           <number:minutes number:style="long"/>
1136           <number:text>:</number:text>
1137           <number:seconds number:style="long"/>
1138           </number:time-style>"""
1139    )
def default_toc_level_style(level: int) -> Style:
150def default_toc_level_style(level: int) -> Style:
151    """Generate an automatic default style for the given TOC level."""
152    tab_stop = TabStopStyle(style_type="right", leader_style="dotted", leader_text=".")
153    position = 17.5 - (0.5 * level)
154    tab_stop.style_position = f"{position}cm"
155    tab_stops = Element.from_tag("style:tab-stops")
156    tab_stops.append(tab_stop)
157    properties = Element.from_tag("style:paragraph-properties")
158    properties.append(tab_stops)
159    toc_style_level = Style(
160        family="paragraph",
161        name=_toc_entry_style_name(level),
162        parent=f"Contents_20_{level}",
163    )
164    toc_style_level.append(properties)
165    return toc_style_level

Generate an automatic default style for the given TOC level.

def hex2rgb(color: str) -> tuple[int, int, int]:
120def hex2rgb(color: str) -> tuple[int, int, int]:
121    """Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B)
122    tuple.
123    Arguments:
124
125        color -- str
126
127    Return: tuple
128    """
129    code = color[1:]
130    if not (len(color) == 7 and color[0] == "#" and code.isalnum()):
131        raise ValueError('"%s" is not a valid color' % color)
132    red = int(code[:2], 16)
133    green = int(code[2:4], 16)
134    blue = int(code[4:6], 16)
135    return (red, green, blue)

Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) tuple. Arguments:

color -- str

Return: tuple

def make_table_cell_border_string( thick: str | float | int | None = None, line: str | None = None, color: str | tuple | None = None) -> str:
216def make_table_cell_border_string(
217    thick: str | float | int | None = None,
218    line: str | None = None,
219    color: str | tuple | None = None,
220) -> str:
221    """Returns a string for style:table-cell-properties fo:border,
222    with default : "0.06pt solid #000000"
223
224        thick -- str or float or int
225        line -- str
226        color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
227
228    Returns : str
229    """
230    thick_string = _make_thick_string(thick)
231    line_string = _make_line_string(line)
232    color_string = _make_color_string(color)
233    return " ".join((thick_string, line_string, color_string))

Returns a string for style:table-cell-properties fo:border, with default : "0.06pt solid #000000"

thick -- str or float or int
line -- str
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

Returns : str

def rgb2hex(color: str | tuple[int, int, int]) -> str:
138def rgb2hex(color: str | tuple[int, int, int]) -> str:
139    """Turns a color name or a (R, G, B) color tuple into a "#RRGGBB"
140    hexadecimal representation.
141    Arguments:
142
143        color -- str or tuple
144
145    Return: str
146
147    Examples::
148
149        >>> rgb2hex('yellow')
150        '#FFFF00'
151        >>> rgb2hex((238, 130, 238))
152        '#EE82EE'
153    """
154    if isinstance(color, str):
155        try:
156            code = CSS3_COLORMAP[color.lower()]
157        except KeyError as e:
158            raise KeyError(f'Color "{color}" is unknown') from e
159    elif isinstance(color, tuple):
160        if len(color) != 3:
161            raise ValueError("Color must be a 3-tuple")
162        code = color
163    else:
164        raise TypeError("Invalid color")
165    for channel in code:
166        if channel < 0 or channel > 255:
167            raise ValueError("Color code must be between 0 and 255")
168    return f"#{code[0]:02X}{code[1]:02X}{code[2]:02X}"

Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" hexadecimal representation. Arguments:

color -- str or tuple

Return: str

Examples::

>>> rgb2hex('yellow')
'#FFFF00'
>>> rgb2hex((238, 130, 238))
'#EE82EE'